Build guide
Self-hosting Postiz on a VPS: a working setup, HTTPS without a domain, and the OAuth trap that will silently break your posting
Postiz is an open-source social media scheduler — the self-hostable answer to Buffer, Hootsuite, and Metricool. It connects your YouTube / TikTok / X / LinkedIn accounts and posts on a schedule, with an API you can drive from your own…
A working setup, HTTPS without owning a domain, and the OAuth trap that will silently break your posting exactly seven days after you finish.
Disclosure: This site uses affiliate links on some pages, and we say so on every page that has one. This page has none. We run Postiz self-hosted and we run it on a VPS, and we have not verified the commission terms of either company's affiliate programme, so every link below is a plain untracked link. When we can read the terms, we will add tracked links and update this box — not before. We would rather have an article you can trust than a link we can't explain.
Postiz is an open-source social media scheduler — the self-hostable answer to Buffer, Hootsuite, and Metricool. It connects your YouTube / TikTok / X / LinkedIn accounts and posts on a schedule, with an API you can drive from your own code. That last part is why we run it: we needed a machine to be able to publish, not a person clicking a calendar.
This is what we learned actually running it in production, including the two things that cost us a week.
Before you start: it does not belong on your laptop
Postiz is not one container. It is an application plus Postgres, Redis, and Temporal — our running instance shows 8 containers. The official docs say the stack has been tested on a 2 GB RAM / 2 vCPU Ubuntu 24.04 VM, which is genuinely modest.
Our own experience differs, and we will state it plainly rather than pick a side: we sized ours at 11 GB RAM / 4 vCPU and never worried about it. We have not tried to run it at the documented 2 GB minimum, so we can't tell you whether that is comfortable or just barely survivable. What we can tell you is that we ruled out running it next to other work on an 8 GB laptop before we started, and we do not regret that. A multi-container stack with its own database and workflow engine is a VPS tenant, not a desktop app.
Any small VPS provider works — we use Vultr, but Hetzner, DigitalOcean and friends are all fine for this. It is Docker; the host is not interesting.
Step 1 — install Docker and get the compose repo
On a fresh Ubuntu 24.04 box:
curl -fsSL https://get.docker.com | sh
Then — and this matters — clone Postiz's compose repository rather than copying a compose file out of a blog post (including this one). The upstream docs are explicit about it: services, images and environment variables change between releases, and a stale compose file is the single most common way this install fails. Take it from the official installation docs.
We are deliberately not reproducing the compose file here for that exact reason. Everything below is the part that doesn't come from the docs.
Step 2 — the URL variables are the whole install
If your Postiz install "works" but the frontend cannot talk to the backend, or OAuth callbacks land nowhere, it is almost always these. Postiz needs to know its own public URL — and it needs the externally reachable URL, not localhost:
MAIN_URL— your public HTTPS base URLNEXT_PUBLIC_BACKEND_URL— the public URL the browser uses to reach the APIBACKEND_INTERNAL_URL— the in-container address the frontend uses server-sideJWT_SECRET— a long random string; generate it, never reuse an exampleDATABASE_URL/REDIS_URL— point at the compose servicesDISABLE_REGISTRATION— see step 5
Generate the secret properly:
openssl rand -hex 32
The app listens on port 4007 by default. Temporal's UI is on 8080 — do not expose that publicly.
Step 3 — HTTPS with Caddy
Every social platform's OAuth requires HTTPS callbacks, so you cannot skip TLS. Caddy is the least painful option because it gets and renews certificates on its own:
postiz.example.com {
reverse_proxy localhost:4007
}
That is the entire config. Caddy provisions the certificate on first request.
Step 4 — HTTPS without owning a domain (the trick)
We did not have a domain when we set this up, and OAuth still needed real HTTPS. The workaround: sslip.io, a free public DNS service that resolves any hostname containing an IP address back to that IP. So if your server is 203.0.113.10:
203-0-113-10.sslip.io {
reverse_proxy localhost:4007
}
Caddy sees a real hostname, provisions a real Let's Encrypt certificate, and you get a genuinely valid HTTPS endpoint with zero registrar involvement and zero cost. Every OAuth provider we tried accepted it as a callback host.
The honest limitations: the hostname is ugly, it is tied to that IP forever (rebuild the server on a new IP and every OAuth app needs its callback URLs updated), and you are depending on a free third-party DNS service staying up. It is an excellent way to get running today and a bad permanent address. Move to a real domain when you have one — a domain is ~$10/year and it is the cheapest part of this project.
Step 5 — lock down registration immediately
A self-hosted Postiz on a public URL has an open signup page by default. Set:
DISABLE_REGISTRATION=true
Create your own account first, then set this and restart:
docker compose down && docker compose up -d
Postiz requires a full down/up for config changes — a restart is not enough.
The trap: your posting will silently die in exactly 7 days
This is the part we wish someone had written down, because it cost us a week and a finished video.
We connected YouTube through Postiz. It worked. We built a clip, scheduled it, and the API returned a post ID and a success response. Days later the video was not on the channel. Not failed — simply not there. The orchestrator logs eventually said it:
Token expired or invalid, please reconnect your YouTube account
Root cause: our Google Cloud OAuth app was in "Testing" publishing status. Google expires refresh tokens after exactly 7 days for testing-mode apps. We connected the channel on the 21st. It died on the 28th. It would have died every 7 days forever.
The fix is not a weekly reconnect. It is a status change:
- Google Cloud Console → your project → Google Auth Platform → Audience → Publish app (moving it from Testing to In production).
- Reconnect the channel in Postiz afterwards. Publishing does not retroactively extend a token that was already issued under Testing — you must disconnect and re-authorise once to mint a non-expiring one. We lost extra days assuming otherwise.
- Unverified-in-production is fine for a single-owner setup. You will see an "unverified app" warning when you authorise your own account. That is the same click-through you already did.
- TikTok has the same shape of problem. Its developer apps start in Sandbox mode, where posts are forced to private visibility — everything appears to work end to end and nothing is ever publicly visible. Check before you rely on it.
The bigger lesson, which is not about Postiz
An API returning success is not proof the action happened. Postiz accepted the post and gave us an ID; YouTube never received it. We told ourselves the clip was live for days on the strength of that ID.
If you automate publishing, verify at the true destination — check YouTube Studio, check the actual profile — not the middleman's acceptance. Build that check into the automation, not into your memory.
Is self-hosting Postiz worth it?
Yes if: you want API-driven posting without a per-seat SaaS bill, you already run a VPS, you are comfortable with Docker and OAuth apps, or you need multiple channels without paying per channel.
No if: you want to schedule a few posts a week by hand. Buffer's paid tier exists and it is cheap and it does not make you register a Google Cloud project. The genuine cost of self-hosting here is not the VPS — it is that you are now the person who has to know about testing-mode refresh tokens and TikTok sandbox visibility. We think that trade is worth it. We also think most people asking should honestly answer "no."
Postiz also offers a hosted plan if you like the product but not the maintenance. We have not used it and cannot compare it, so we are not going to rank it.
Checklist
- [ ] VPS with Docker. Official minimum is 2 GB / 2 vCPU; we ran it comfortably with far more headroom.
- [ ] Clone the compose repo from the official docs — do not copy a compose file from any blog.
- [ ] Set
MAIN_URL,NEXT_PUBLIC_BACKEND_URL,BACKEND_INTERNAL_URLto the public URL. - [ ]
JWT_SECRETfromopenssl rand -hex 32. - [ ] Caddy in front, port 4007 proxied, Temporal's 8080 not exposed.
- [ ] No domain?
<dashed-ip>.sslip.iogets you real Let's Encrypt HTTPS today. - [ ] Create your account, then
DISABLE_REGISTRATION=true, thendown && up. - [ ] Publish your Google OAuth app to Production, then reconnect the channel.
- [ ] Check whether your TikTok app is still in Sandbox before trusting a "successful" post.
- [ ] Add a verification step that checks the platform, not the API response.
Written from a production self-hosted instance, July 2026. Postiz changes quickly — check the official docs for anything version-specific, and tell us if something here has gone stale.