Common issues when self-hosting Klonkt, and how to fix them.
No followers come through from Mastodon, nothing federates, your profile looks empty to remote servers.
node -v must be v20 or higher. On older Node the federation code can't run at all (it uses APIs added in Node 18+) — yet the normal site and individual posts still load, which makes this easy to miss.PUBLIC_BASE_URL in .env to your public address, e.g. PUBLIC_BASE_URL=https://yourdomain.com. Federation builds absolute URLs (actor, notes, inbox) from it — without it there's nothing valid to publish. Restart after changing./ap/* and /.well-known/* through to the app (don't strip or rewrite them).The homepage / archive render oddly behind a proxy, but a single post URL is fine.
NODE_ENV=production in .env — this makes the app trust your proxy's X-Forwarded-* headers and set secure cookies.Host header (your domain, not localhost). Caddy's reverse_proxy does this by default; with nginx add proxy_set_header Host $host;.yoursite.com/ returns 404, yet yoursite.com/your-post-slug loads fine.
This is almost always your reverse proxy or tunnel — not Klonkt. The app's homepage is never a 404: it shows your feed, or a setup/welcome page if no site exists yet. A 404 means the bare / request never reaches the app (while deeper paths do). Confirm by hitting the app directly, bypassing the proxy, on the machine running Klonkt:
curl -i http://localhost:3000/
/. Make sure it proxies all paths, not just sub-paths — a location / that does try_files or serves a static root will 404 the bare path while deeper URLs still proxy through. nginx: location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; }. Caddy: reverse_proxy 127.0.0.1:3000 (no path matcher).The proxy (Caddy/nginx) returns 502 — it reached the proxy, but the app behind it didn't answer.
Unlike a 404, a 502 means the proxy is configured right (it is forwarding) but the upstream is down or unreachable. Check, in order, on the proxy host:
ss -ltnp | grep 3000. Nothing there → the app (or your SSH tunnel, if you forward from another machine) is down.curl -i http://127.0.0.1:3000/. Connection refused → the app isn't up (it may have crashed — an old Node version is a common cause). A page → the proxy's upstream points at the wrong port.autossh -M 0 -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -R 127.0.0.1:3000:localhost:3000 host, and keep the source machine awake (caffeinate on macOS). A reverse-tunnel binds to 127.0.0.1 on the proxy host, so the proxy's upstream must be 127.0.0.1:<port> (not 0.0.0.0).Klonkt logs to standard output (the console) — there's no separate log file. Where that goes depends on how you run it:
journalctl -u klonkt -f (live), journalctl -u klonkt -n 200 --no-pager (last 200 lines), systemctl status klonkt (is it running? + why it crashed).npm start in a terminal: the log is right there in that window — if it crashed, the stack trace is the last thing printed, so scroll up. Keep a copy with npm start 2>&1 | tee klonkt.log.pm2 logs klonkt, or the files under ~/.pm2/logs/klonkt-*.log.docker compose logs -f (or docker logs -n 200 <container>).For a 502 or a site that won't start, the cause is almost always the first error at boot here — an old Node version, a missing .env value, or a port already in use.
.env but nothing changedThe config is read once, at startup. Restart the app: pm2 restart klonkt (or docker compose up -d, or your service manager).
better-sqlite3 / native module errors"Cannot find module", "was compiled against a different Node.js version", crashes on boot.
The database driver is a native module tied to your Node version. After installing or switching Node, rebuild it:
npm rebuild better-sqlite3
A manual build also needs compiler tools present (build-essential + python3 on Debian/Ubuntu). The Docker image and the one-command installer handle this for you.
Reset it from the server (no email needed) — it prints a new password:
npm run reset-admin
A record (IPv4) and ideally an AAAA record (IPv6).localhost.Set a free port in .env: PORT=3001 (then point your reverse proxy at that port). The one-command installer picks a free port automatically.
Klonkt automatically sends your recent posts to a new follower, so their timeline fills in. Older posts appear gradually as Mastodon refreshes — this is normal: Mastodon doesn't fetch a full history on follow. New posts always federate immediately (with PUBLIC_BASE_URL set).
ffmpeg is bundled — you don't install it separately. If you want a lightweight blog / photo / EPK / links site with no audio at all, set KLONKT_AUDIO=off.
How logins work, and two things to get right in production:
httpOnly (JavaScript can't read it) and sameSite=lax (CSRF protection). The same server accepts that cookie via any hostname pointing to it; sessions aren't bound to a hostname (so apex + www + a custom domain all work on one instance).NODE_ENV=production so the cookie is marked Secure (sent over HTTPS only) and the app trusts your reverse proxy. Without it the cookie can travel over plain HTTP and be intercepted.SESSION_SECRET. Klonkt auto-generates one (stored in storage/.session-secret). If you run several different sites, never copy the same SESSION_SECRET into more than one — a login cookie from one would then be accepted by the others.Still stuck? Email info@robingenis.com or open an issue on GitHub.
← back to install