Self-hosting
Troubleshooting
The failures people actually hit, and what each one means.
Start here, always:
docker compose ps # what is running
docker compose logs -f backend # or: caddy, frontend, postgres, redis
Installation
The first up takes a long time or seems stuck.
The first build compiles the images — a few minutes is normal. Watch progress
with docker compose logs -f.
A service keeps restarting.
Read its logs specifically. A restarting backend is usually a database it cannot
reach; a restarting postgres is usually a pgdata volume that does not match
the password in secrets (see Backups).
Reaching the site
I cannot reach it at all.
- Local: use
http://localhost(not HTTPS). The app is also directly athttp://localhost:5173for debugging. - Public: confirm the DNS
A/AAAArecord points at the server, and that ports 80 and 443 are open in both the host firewall and your provider’s security group.
Ports 80/443 already in use.
Set HTTP_PORT / HTTPS_PORT in .env to free ports, or stop the conflicting
service.
HTTPS
The certificate is not being issued.
- DNS must resolve to this server first.
- Ports 80 and 443 must be reachable from the internet — Let’s Encrypt validates over them.
- Check the ACME error:
docker compose logs -f caddy.
localhost has no certificate.
Expected. Local development is served over plain HTTP.
“Port 25 is blocked” when setting up direct email.
Expected on most hosts, and always on localhost. Use the smtp or relay mode
instead, or ask your provider to unblock port 25 and set reverse DNS.
The test email fails. The error is shown verbatim. Common causes: wrong SMTP password, the wrong port/TLS combination (587 = STARTTLS, 465 = implicit TLS), or a provider that requires a verified sender. Fix and press send test again.
Mail sends but lands in spam.
With direct, check SPF, DKIM, DMARC, and especially the PTR record. With any
mode, a brand-new IP or domain has no reputation yet.
A visitor reports “This instance can’t send email”.
That is the console-mode notice, not a fault: the instance is still on its
default transport and only prints mail to the backend log. Configure smtp,
relay, or direct, press send test email, and the notice disappears for
visitors.
Federation
The federation toggle did not take effect.
It applies on restart. Run docker compose up -d, then check
curl localhost:8000/version for "federation": true.
/.well-known/nodeinfo returns 404.
Federation is still off, or the restart has not happened.
The instance does not appear in FediDB, instances.social, or
fediverse.observer.
Those crawl /.well-known/nodeinfo from the public internet, follow the link in
it, and read the document it names. Check all three answer from outside your
network:
curl https://your-domain/.well-known/nodeinfo # -> a links array, not an empty one
curl https://your-domain/nodeinfo/2.1
curl https://your-domain/nodeinfo/2.0
If all three answer here but the directory still shows nothing, the remaining delay is on its side: re-crawls are typically on the order of a day, and a directory generally has to have discovered the instance first — either because another server federated with it, or because the operator submitted it through the directory’s own form.
Nobody can find @me@my-domain from Mastodon.
Check, from another machine:
curl "https://your-domain/.well-known/webfinger?resource=acct:me@your-domain"
curl -H "Accept: application/activity+json" https://your-domain/users/me
Both must return JSON over HTTPS on your real domain. If the domain in the returned URIs is wrong, your public domain setting does not match the domain being served.
Keep the Accept header on the second one. Without it that URL redirects to the
profile page, by design — so a bare curl -L returns HTML and looks like a
failure when nothing is wrong.
Posts are not reaching remote followers. Delivery is queued. Check the backend logs for delivery errors, confirm the remote instance has not defederated you, and confirm the post is published — not a draft, and not still waiting for a scheduled time.
Scraper shield
The toggle in Admin → Security is unavailable. Caddy has not been recreated with the newer Caddyfile. Run once:
docker compose up -d --build --force-recreate
The challenge page is stuck on “Calculating…” and only loads after a refresh.
Fixed in the release that added ANUBIS_REDIRECT_DOMAINS. The fix is a Caddyfile
change, so a plain up -d does not apply it — recreate once:
docker compose up -d --build --force-recreate
My uptime monitor started reporting 503.
Check what it is fetching. A challenged request is answered with 503 and
Retry-After by design — see
what a challenged request gets back.
Nothing a reader loads is challenged, so a monitor pointed at /, an article, or
/healthz will never see one; a monitor pointed at /login or /admin will see
one on every check. Point it at a page rather than at the sign-in form.
Readers are being challenged again after a restart.
Not expected. The signing key lives in the secrets volume and survives
restarts. If it recurs, the challenge service is failing to read that key —
check its logs for ED25519_PRIVATE_KEY_HEX_FILE or generating random key.
Deleting the secrets volume also loses the key, and with it the Postgres
password and session secret.
A visitor says the challenge page never finishes loading.
Solving it needs JavaScript. Either theirs is off — in the browser, or blocked
for your domain by an extension — or the challenge script itself failed to load,
which a reload fixes. The screen’s footer says both, and links them back to your
home page; reading is never challenged, so that link works whatever their browser
is doing. Put your own support address in the impressum.page body of
botPolicy.yaml if you want one on that screen.
The challenge screen looks nothing like my site.
Caddy serves anubis-theme.css at the path the challenge page asks for; when
that file is missing, or the Caddy container predates it, the challenge service
answers with its own stylesheet instead and the screen reverts to its stock
look. git pull restores the file, and Caddy picks it up on the next request —
unless the container is older than the mount, in which case recreate it:
docker compose up -d --force-recreate caddy
I edited botPolicy.yaml and nothing changed.
The rules are read once at startup and the file is mounted into the container, so
it keeps serving the old set until it restarts:
docker compose restart anubis
Every page load returns 500 while the shield is on.
The challenge service could not determine the client IP. Its logs will say
X-Real-Ip header is not set. Caddy supplies that header, so this means Caddy is
running an older Caddyfile — recreate once with the command above.
Link previews
A shared link shows the domain and the raw URL instead of a card. The fetcher never parsed the page. Fetch it the way the app does and compare:
curl -sA 'WhatsApp/2.23.20.0 A' https://your-domain/@you/your-post \
| grep -o '<meta property="og:[a-z]*" content="[^"]*"'
If the tags are missing and the response is small, check the status: a 503
whose title is “Making sure you’re not a bot!” means the
scraper shield answered instead of the article. That
should not happen — post URLs are not challenged — so look at whether
botPolicy.yaml has been customised. If the tags are there,
the fetcher’s own cache is the likely culprit: preview caches are keyed by URL,
so a card built while the site was down or mid-upgrade sticks. Share the link
with a query string appended (?v=2) to force a fresh fetch — if the card is
correct then, it was cached.
The card has a title and description but no image.
Check the og:image URL directly: it must return 200, an image/*
content-type, and be under about 600 KB. Then check it is not blocked — a
crawler reads robots.txt before fetching an image, and an image under a
disallowed path is silently skipped.
A post with no pictures shows the instance’s logo rather than a card.
Expected in two cases, and neither is a fault. A federated copy of someone
else’s post is illustrated by the instance that published it, not by yours. And
the card is drawn in one bundled font covering Latin, Greek and Cyrillic — a
title in Japanese, Korean, Arabic or Hebrew cannot be set in it, and the logo
is served instead of an empty rectangle. Anything else, fetch the card
directly: /api/og/posts/<post-id>.jpg should answer 200 image/jpeg, or
302 to /og-image.png.
Data
I ran docker compose down -v.
That deleted the volumes: database, uploads, secrets, and certificates. Restore
from a backup; there is no other recovery.
The backend cannot connect to Postgres after a restore.
The classic pairing failure: a fresh pgdata with an old secrets, or the
reverse. See the pairing rule in
Backups and restore.
Still stuck
Collect the version and the relevant logs before opening an issue:
curl localhost:8000/version
docker compose logs --since 30m backend > backend.log
Then open an issue at github.com/the-jk-labs/omicron/issues.
Found a mistake?Edit this page on GitHub.