When something looks wrong, the fastest path back to safe is almost never a clever fix — it’s to stop publishing, look at the evidence, then act. This page walks you through that, calmly and in order.
The one move to remember. If anything feels off — wrong posts going out, a platform acting up, or you just aren’t sure — freeze publishing first with touch /opt/portal/PAUSE, then diagnose. Nothing paid or public happens while that file exists, and lifting the pause is a single rm when you’re ready. You can always undo a pause; you can’t un-send a post.
First, don’t panic — the kill switch
The system is designed so that the scariest-looking situation has the calmest first response. Before you SSH anywhere, before you read a single log, before you try to reason about what broke: stop the publishing.
ssh root@64.227.29.192
touch /opt/portal/PAUSE
# That's it. publish_due.py now publishes NOTHING on its next run.
The PAUSE file is the universal circuit-breaker. As long as it exists in /opt/portal/, the every-10-minutes publisher checks it first and does nothing — no X posts, no Bluesky posts, nothing for any tenant. Drafting, feeds, and reports keep running quietly in the background, so you lose no work; you only stop the outbound step. That means pausing is free and reversible, which is exactly why it’s the first move rather than a last resort.
Once publishing is frozen, you have all the time in the world to figure out what’s actually happening. Work through the health checks and the gotcha table below. When you’ve confirmed the system is healthy and doing the right thing, resume with a single command:
rm /opt/portal/PAUSE
# Publishing resumes on the next 10-minute cycle.
A pause is not a failure. Leaving
PAUSE in place overnight while you sleep on a decision costs you nothing but a day of posts. The queue waits patiently. There is no penalty for being cautious — see
Playbook 11 for the full stop/resume procedure.
Known gotchas — symptom, cause, fix
These are the real failure modes this system has actually hit, with the fix that resolved each one. If your symptom is on this list, start here — it’s almost certainly one of these before it’s something exotic.
Read the table before you improvise. Every row here cost real time to diagnose the first time. Matching your symptom to a known cause takes seconds and skips the guesswork.
Health checks — is the hub actually alive?
Three quick commands tell you almost everything about whether the publishing brain is healthy. Run them from the hub after SSH-ing in. They answer, in order: are the containers running, is the web front door responding, and is the publisher doing its job?
1. Are the containers up?
ssh root@64.227.29.192
docker ps
# Expect to see: postiz, postiz-postgres, postiz-redis, temporal
# If 'temporal' is missing -> that's your 502 on /api.
2. Is the web front door answering?
curl -I https://postiz.wholetech.com
# Expect a 200/302 header, not a 502.
# Use https:// -- http won't behave (and the cookie won't stick on login).
3. Is the publisher working?
tail -n 40 /var/log/portal-pub.log
# Shows the most recent publish_due.py runs: what went out,
# what was held, and whether PAUSE is in effect.
Read them together. Containers up + front door 200 + a clean publisher log = a healthy system. If the front door 502s, jump straight to docker ps and look for Temporal. If containers are fine but posts aren’t going out, the answer is in the publisher log (often just PAUSE, or the X cap).
Where the logs are
When you need to know what the system did — not what it was supposed to do — the logs are the truth. Each step of the nightly chain writes its own log, split across the two servers. Match the question you have to the log that answers it.
A useful habit: tail -f a log to watch it live, or tail -n 50 for the recent history. The publisher log (portal-pub.log) is the one you’ll open most — it’s where “is it working right now?” gets answered.
Recovery runbooks
Four situations come up often enough to have a written path through them. Each one is short on purpose — the goal is to get you back to a known-good state without improvising.
After a server reboot
Both the DigitalOcean droplet reboots and Docker’s restart policy are set up to bring the system back on their own. The containers auto-restart, and cron picks the nightly chain back up on its normal schedule — you don’t need to hand-start anything. What you should do is verify. Run the three health checks: docker ps to confirm all four containers (especially temporal) are back, curl -I https://postiz.wholetech.com for a clean header, and a glance at portal-pub.log. If all three are green, the reboot recovered cleanly and there’s nothing more to do.
If posting misbehaves
Wrong content, a duplicate, or a platform doing something unexpected — the sequence is always the same: freeze, then look, then resume.
- Freeze:
touch /opt/portal/PAUSE. Now nothing new goes out while you investigate.
- Look: read
portal-pub.log to see exactly what published and when, and portal-gen.log to see what was drafted vs. held. The evidence is almost always right there.
- Resume: once you understand and have corrected the cause,
rm /opt/portal/PAUSE. Publishing restarts on the next cycle.
If the problem was a specific post rather than the system, the approval controls (Approve / Edit / Skip) are the right tool — see Playbook 3.
If the feed looks stale
Stale or repetitive posts usually mean the nightly feed didn’t refresh — the engine is faithfully working from yesterday’s data. Check social-feed.log on MAIN first: did gen-social-feed.py run at 05:00 UTC, and did it ship the feed to the hub? If the build failed or didn’t transfer, re-run the builders to rebuild the feed and re-draft from it. The full step-by-step is in Playbook 8 — follow it rather than piecing the commands together from memory.
Remember the data flow. Feed (MAIN, 05:00) → draft (HUB, 05:10) → publish (HUB, every 10 min). Stale posts almost always trace back to a stale feed one step upstream, so start at MAIN.
If the hub is lost
If the hub droplet is unrecoverable — not just down, but gone — this is a restore-from-backup situation, not a live-fix one. A nightly backup (backup.sh, 04:17 UTC) copies the database, portal data, and config off-box to cloud object storage precisely for this. The disaster-recovery procedure — provisioning a fresh box and restoring from that backup — lives on the Security, Privacy & Compliance page. Go there; don’t try to reconstruct the hub from memory.
Don’t improvise a rebuild. The hub holds connected-account tokens and tenant config. Restoring from the verified backup is the correct path — a hand-rebuilt box risks losing connections and mismatching config.
How to tell it’s actually working
Troubleshooting isn’t only for when something breaks — it’s just as valuable to confidently recognize “this is fine.” Here are the positive signals that mean the system is healthy and doing its job.
- Four containers running.
docker ps shows postiz, postiz-postgres, postiz-redis, and temporal. That’s the whole brain, present and accounted for.
- The front door answers cleanly.
curl -I https://postiz.wholetech.com returns a normal header, not a 502.
- Fresh posts appear on the connected accounts. New content on Bluesky (uncapped, free) every day, and on X up to the daily cap of 3 paid posts. The very first live auto-post was verified going to both X and Bluesky — that’s the shape of “working.”
- The publisher log is boring.
portal-pub.log shows regular runs, posts going out, and held items flagged — no repeating errors, no crash loops.
- Held posts, not wrong posts. Seeing items held for a missing field isn’t a bug — it’s the honesty guard working exactly as intended. The system never guesses a fact; a post it can’t verify is held, never faked.
- No phone alerts. The ntfy channel stays quiet. Failures push a notification; silence is good news.
Frequently asked questions
Does it really post without me?
Yes. The owner’s own network runs on auto-fire: every night it builds a feed from real data, drafts posts, and publishes anything due — no human in the loop. You can layer an approval step on for any tenant (a private phone-friendly queue where you Approve / Edit / Skip), but the default is fully hands-off.
Will it ever make up a fact?
No — and this is the core trust feature. Every template declares the fields it needs. If any required field is missing from the source data, the post is held, flagged, and never published. It states nothing it can’t verify from source. A post with a hole in it waits; it never gets a guess.
What does it cost?
The whole system is $24/month — that’s the hub droplet. Content generation is genuinely free: the templates are deterministic, so generating 1,000 posts costs the same as generating one (nothing — no per-token AI billing). The only paid channel is X, at roughly 1.5 cents a post (about 20 cents with a link), and it’s capped at 3 paid posts a day. Everything else — Bluesky, Mastodon, LinkedIn, Meta — is free.
Can I manage clients now?
Yes — it’s multi-tenant by design. Each client is one block in clients.json with their own brand, cadence, channels, and optional approval step, fully isolated from every other tenant. Because content generation is free, adding clients doesn’t raise your costs. See Managing Clients for how to sell and run it as a service.
How do I stop it fast?
One command: touch /opt/portal/PAUSE. That freezes all publishing for every tenant immediately — it takes effect on the next 10-minute publisher cycle. Resume with rm /opt/portal/PAUSE. See Playbook 11.
Which networks are live right now?
Bluesky and X are connected and posting. Mastodon is registered with the account connect pending. LinkedIn and Meta (Facebook + Instagram) are gated behind their platforms’ app-review processes. Postiz can also drive Threads, Pinterest, TikTok, YouTube, Reddit and more — each needs its own developer app first. Full status is on Connecting Social Platforms.
What happens if X runs out of credit?
X quietly stops posting — and that’s expected, not a fault. Because X is the only paid channel, an empty credit (or hitting the 3-post daily cap) simply pauses X. Bluesky and every free channel keep right on going. Top up the X credit whenever you choose; nothing else is affected.
How do I see what actually went out?
Read portal-pub.log on the hub — it records every publish, the channel, and the time. For a rendered view of the queue and history, use the approval portal, or see Playbook 12 for the “see what posted” procedure.
Escalation — where to look when it’s serious
Most problems never need escalating — the gotcha table and health checks resolve them. But when something is genuinely off and you’re not sure why, here’s where the system is already trying to tell you what happened.
Check ntfy first
The system pushes a notification to a phone channel via ntfy when a nightly step fails. So before you go digging, check whether it already alerted you — an ntfy message usually points straight at the failed step, which points you at the right log. No alert plus stale output usually means the step ran but produced nothing (check the feed), rather than a hard crash.
Then read the log for that step
Every step logs. Match the alert (or your symptom) to the log table above: feed problems → social-feed.log on MAIN; drafting → portal-gen.log; publishing → portal-pub.log; the digest → portal-rep.log. Results and errors for each run are written there — that’s where the actual answer lives.
The escalation loop, in one breath. Freeze with
PAUSE → check ntfy for what alerted → open the matching log → match the symptom to the
gotcha table → fix → verify with the
health checks →
rm PAUSE to resume. Calm, in order, evidence-first — that’s the whole discipline.
For anything involving secrets, tokens, or the disaster-recovery restore, don’t improvise — those procedures live on the Security, Privacy & Compliance page, and the step-by-step tasks are all on Playbooks.