What Is Postiz? Self-Hosting Your Social Scheduling (and the Gotchas Nobody Warns You About)

If you publish anything regularly — blog posts, videos, the occasional thread — you eventually want
to schedule it instead of babysitting six browser tabs at the right hour of the day. The usual
answers are Buffer, Hootsuite, Later: hosted SaaS, monthly fee, your content living on someone
else's server, and a per-channel limit that creeps up the price the moment you get serious.
Postiz is the open-source answer to that. It's a self-hostable social media scheduling tool —
you run it on your own machine, connect your channels, write your posts, and it publishes them on a
schedule. Same job as Buffer, except the database is yours, the API key is yours, and the only limit
is the box it runs on.
I moved my channels onto a self-hosted Postiz instance, and it's genuinely good. It also handed me a
few lessons the getting-started guide doesn't mention. This post is both: what Postiz is, and the
four things I wish I'd known on day one.
What Postiz actually is
At its core Postiz is a calendar with a queue behind it. You:
- connect integrations — each social channel (Mastodon, YouTube, X, Facebook, LinkedIn, Bluesky,
Instagram, TikTok, and a long tail of others) is an integration you authorize once via OAuth; - compose posts — text, media, per-channel variations;
- schedule — drop them on the calendar, or let Postiz space them out;
- let it publish — a background worker (Redis-backed) picks up each post at its
publishDate
and pushes it to the platform.
It ships as a set of Docker containers: the app itself, a PostgreSQL database, and a Redis instance
for the queue. A docker compose up and a reverse proxy with TLS, and you have your own little
publishing hub. There's a clean web UI, and — important for what follows — a public REST API so
you can drive it from scripts.
That public API is why I like it. I keep my content pipeline in source control, so being able to
POST a scheduled post from a script, or pull a status dashboard with a GET, matters more to me
than any UI nicety.
Why self-host it at all
A fair question — SaaS is less work. My reasons, in order:
- Your content isn't a hostage. Drafts, scheduled posts, the whole calendar live in your
Postgres. No platform deciding tomorrow that your tier now costs double. - No per-channel tax. Hosted schedulers charge by the channel and the seat. Self-hosted, you add
channels until the box complains. - It's scriptable on your terms. One API key, your own automation, no rate-limit-as-a-product.
The cost is the cost of self-hosting anything: you are now the ops team. Which brings me to the
gotchas.
Gotcha #1: YouTube disconnects every seven days (and it's not Postiz's fault)
The symptom looked like a Postiz bug. My YouTube channel would publish happily for about a week,
then quietly start failing every upload with a refreshNeeded error. Reconnect it, and exactly
seven-ish days later — dead again. Meanwhile Mastodon never so much as hiccuped.
The clean seven-day cadence is the tell. This is Google's rule, not Postiz's: when your Google
Cloud OAuth app is left in "Testing" publishing status, Google expires the refresh token after
exactly 7 days. Postiz uses that refresh token to silently renew access; once Google kills it,
every call fails. Mastodon doesn't do this because its tokens don't expire the same way — which is
exactly why it stayed green while YouTube died on a weekly timer.
The fix is one setting, and it is not the scary full-verification flow:
- Open the Google Auth Platform → Audience tab for the project that owns your YouTube OAuth
client. (In the new console UI, the Testing-vs-Production toggle moved here from the old "OAuth
consent screen" page.) - Set Publishing status to In production.
- Reconnect the channel once so Postiz captures a fresh, now-long-lived token.
"In production" alone removes the 7-day cap. You'll click through an "unverified app" warning on
reconnect — cosmetic for your own account, no effect on token lifetime. A two-minute change that
ends a weekly chore.
One subtlety that cost me a minute: the OAuth client lives in a specific Google Cloud project,
identified by the number that prefixes your client ID (451508066695-….apps.googleusercontent.com
→ project451508066695). If you land on the wrong project you'll see "not configured yet" and
assume something's broken. It isn't — you're just in the wrong project.
Gotcha #2: Postiz has two auth systems, and they don't overlap
This one I only understood by reading the source. Postiz exposes two APIs with two different
credentials:
- The public API (
/api/public/v1/*), authenticated by the API key you mint in settings.
Durable, simple — but a deliberately limited route set: list posts, create a post, upload media,
delete, set status. Great for the 90% case. - The internal API (
/api/*), which is what the web UI itself calls, authenticated by your
session token (theauthcookie). This is the full surface — everything the UI can do.
The public API key gets a flat 401 on internal routes. So the moment you need something the UI
can do but the public API doesn't expose, the API key isn't enough — you need the session token.
The happy surprise: that session token is a JWT signed with no expiry. In the code it's literally
sign(value, JWT_SECRET) with no expiresIn, which means no exp claim, which means it's valid
until someone rotates JWT_SECRET. So you can grab the auth cookie value once (DevTools →
Application → Cookies), stash it in an environment variable next to your API key, and treat it as a
permanent credential. There's no "generate internal token" button — the cookie is the token.
Gotcha #3: you can't reschedule through the public API
Here's where #2 stopped being trivia. After fixing the YouTube token, I had a backlog of ~35 uploads
that had piled up bunched — four on some days, two of them colliding on the exact same minute.
Left alone, they'd all fire in a burst the moment the channel came back. I wanted them re-spaced to a
calm two-a-day.
The public API can create and delete posts, but it has no reschedule. Rescheduling — the thing
the calendar does when you drag a post — is an internal route:
PUT /api/posts/{id}/date
{ "date": "2026-06-17T15:00:00.000Z", "action": "schedule" }
action: "schedule" sets the post's state back to QUEUE at the new date and preserves all of its
content and media. It's exactly a calendar drag, done programmatically. And because it's an internal
route, it needs the session token from gotcha #2 — the API key won't reach it.
Once I had that, the drain was a small script: read the queued posts with the API key, compute a
clean two-per-day schedule, and PUT each one's new date with the session token. Thirty-five posts,
re-spaced across eighteen days, zero collisions, no burst. The backlog now drips out on its own.
The lesson generalizes: the public API is for the common path; the real power is the internal API,
gated behind the session JWT. Once you know both exist, a lot of "Postiz can't do X from a script"
problems turn into "X is on the other API."
Gotcha #4: some platforms won't let a personal account in at all
This is the one that has nothing to do with Postiz and everything to do with the platforms — but it
will stop you cold, so it belongs here. Postiz can only do what each network's API allows, and
several of the big ones flatly refuse to let a plain personal account publish through their API.
They require a business, creator, or "professional" account first, and often an app review on top
of that.
The usual suspects:
- Instagram — content publishing through the API only works for an Instagram Business or Creator
account, and it has to be linked to a Facebook Page. A personal IG account cannot be automated,
full stop. You'll also need theinstagram_content_publishpermission granted through Meta's app
review. - Facebook — you post to a Page, not your personal profile. That means a Facebook app with the
Pages permissions, and Meta frequently gates those behind business verification (uploading
company documents) before they'll switch the permissions on in production. - TikTok — a developer app whose content-posting scope has to pass an audit, and the account
side expects a business/creator footing. Until the audit clears, you're stuck in a sandbox that only
posts to the app owner's own account. - LinkedIn — company-page posting and the relevant scopes go through an app-review process too.
By contrast, the low-friction channels are the open or creator-first ones: Mastodon (you make a
token in your own settings, done), Bluesky, and YouTube once you've sorted gotcha #1. That
split is worth knowing before you plan your channel mix, because the practical consequence is real:
connecting Mastodon is five minutes; getting Instagram and Facebook genuinely publishing can mean a
business account, a linked Page, document-based business verification, and a multi-day app review —
none of which Postiz can do for you. Postiz will happily show the connect button; whether the platform
honors it depends entirely on what kind of account is on the other end.
The takeaway: decide which accounts need to be "business" before you start wiring them up. If a
channel won't grant API publishing to your account type, no amount of Postiz configuration will fix
it — that's a decision you make on the platform's side, sometimes with a credit card or a company
document, long before the post ever reaches the queue.
Should you use it?
If you want scheduling without renting it, yes — Postiz is a solid, actively developed,
genuinely-open tool, and running it is a normal three-container Docker affair. Just go in knowing:
- Publish your Google OAuth app to production before you connect YouTube, or you'll be
reconnecting it every week. - There are two APIs. The key opens the front door; the session JWT opens the rest of the house.
- The session token never expires — capture it once, store it like any other secret, and the
internal API is yours to automate. - Check the account type each platform demands before you build your channel mix. Mastodon and
Bluesky take five minutes; Instagram, Facebook, TikTok and LinkedIn may need a business/creator
account, a linked Page, business verification, and an app review that Postiz can't do for you.
None of these are dealbreakers. They're the kind of thing you only learn by running the thing in
anger — which, after all, is the whole point of self-hosting. You trade a monthly invoice for an
afternoon of understanding how your tools actually work. I'll take that trade every time.