Deployment Troubleshooting
This guide helps resolve common issues when deploying or running TurboPanel in production. For security-related issues, see Security. For setup verification, see Control plane and Daemon setup.
Introduction
Production issues typically fall into TLS/Caddy misconfiguration, instance socket permissions, daemon WebSocket connectivity, Postgres socket access, or Ansible playbook failures. Self-hosted browser entrypoint is HTTPS on port 8443 by default (Caddy). lets_encrypt serves :443 (plus :80 for HTTP-01). On co-located dev hosts only, Caddy also serves a plaintext mirror at http://<host>:8880 when TURBOPANEL_DEV_HTTP_CONTROL_PLANE=1.
| Port | When |
|---|---|
| 8443 | Default HTTPS (self_signed / upload) |
| 443 + 80 | lets_encrypt (must be free on the control-plane host) |
| 8880 | Co-located dev plaintext mirror only |
Port conflicts (8443 / 8880 / 80 / 443)
Problem: Caddy cannot bind :8443 or (dev) :8880. In lets_encrypt, it cannot bind :80/:443.
Solutions:
- Change
CADDY_PORT/CADDY_HTTP_PORTin instance/Caddy env and update firewall rules - Check for existing listeners:
ss -tlnp | grep -E '8443|8880|80|443' - Stop conflicting services or remap the published port in your reverse proxy
UI or API not loading
Problem: Browser shows errors at https://<host>:8443 by default, https://<hostname>/ in lets_encrypt, or (co-located dev) http://<host>:8880.
Solutions:
- Check instance:
systemctl status turbopanel-instance - Check Caddy:
systemctl status turbopanel-caddy - Test health over HTTPS:
curl -k https://localhost:8443/api/health(omit-kwhenTURBOPANEL_TLS_PUBLICis set) - Co-located dev only: test plaintext mirror:
curl http://localhost:8880/api/health(requiresTURBOPANEL_DEV_HTTP_CONTROL_PLANE=1; 403 when the flag is off) - Logs:
journalctl -u turbopanel-instance -u turbopanel-caddy -f - Static UI: ensure
ui/distexists ifTURBOPANEL_UI_MODE=static - Dev UI: ensure
turbopanel-uiis running whenTURBOPANEL_UI_MODE=dev
Daemon shows offline but service is running
Problem: UI badge is Offline (or Update fails) while systemctl status turbopaneld is active and the Cell panel shows recent activity.
Cause: The server list uses a Postgres presence projection that can drift from live WebSocket state after a failed upgrade or reconnect.
Solution: Re-run the release installer — see Refresh a stuck daemon.
Daemon not connecting
Problem: Remote server daemon fails to register.
Solutions:
- Verify instance URL:
TURBOPANEL_INSTANCE_URL=https://<host>:8443by default, orhttps://<hostname>/inlets_encrypt. On co-located dev only,http://<host>:8880whenTURBOPANEL_DEV_HTTP_CONTROL_PLANE=1(no CA required) - Test from node over HTTPS:
curl -k https://<host>:8443/api/daemon/v1/version(omit-kwhen the origin is publicly trusted) - Co-located dev only: test plaintext:
curl http://<host>:8880/api/daemon/v1/version - Confirm platform CA (HTTPS path, default mode): compare
/etc/turbopanel/instance-ca.pemwithGET /api/daemon/v1/instance/ca(bundle, current CA first). A 404 means public TLS — use the system trust store and do not installinstance-ca.pem. - Firewall: allow 8443 by default from remote daemons to instance (443 in
lets_encrypt; 8880 is dev-only and not for remote production nodes) - Logs:
journalctl -u turbopaneld -f
Stale platform CA (tls-trust parked)
Problem: Daemon log shows tls-trust parked (dialed host, CA path, fingerprint) and reconnects only every 5 minutes to 1 hour — not a silent 30 s loop.
Cause: The host still trusts an old platform CA after the control plane rotated (or the leaf SAN no longer matches the dialed hostname). Control-plane identity (JWKS JWT) is unchanged; only the transport anchor is stale.
Solutions:
- Prefer the overlap path: on the control plane, keep the old CA in
ca-bundle.pemand enqueueserver.tls.trust.reconcilewhile the existing WSS session is still valid. - If the session is already dead, re-run the installer with
--instance-ca(or--insecure-tlsfor bootstrap fetch).run.shpins--cacertfirst; on HTTP000it retries once unpinned and installs only if the fetched PEM validates the live leaf. Old and new fingerprints are printed when$CA_PATHis replaced. - If verification fails, keep the existing CA and fix the instance URL / SAN list rather than blindly replacing the file.
Postgres connection failures (self-hosted)
Problem: Instance cannot connect to Postgres.
Solutions:
- Check Postgres container:
docker ps | grep postgres - Verify Unix socket under
/var/run/turbopanel/postgres/ - Confirm
TURBOPANEL_DATABASE_URLinturbopanel-instance.service— a full Postgres URL, typically injected by theinstance-launchrole - Socket directory permissions:
/run/turbopanelshould be2770 tp:tp
Install wizard / PAM failures (Deno)
Problem: Install bootstrap rejects host credentials.
Solutions:
- Install
pamtesteron the host - Confirm
tpctrluser sudoers allowspamtester login * authenticate - Use a host account in
sudo/wheel/admin, orroot
UI Update stuck on "update already in progress"
Problem: After clicking Update, Ansible runs but the daemon keeps the same
PID for a long time. A second update attempt returns update already in progress.
Cause: The UI update path uses run.sh --no-start (does not stop the daemon
during reconcile). The daemon then tries to restart itself with plain
systemctl enable --now as the tp user — that fails silently
(non-root cannot restart systemd units; enable --now also skips restart when
already active). An in-memory lock then blocks further updates.
Check logs on the server:
sudo tail -30 /var/log/turbopanel/daemon.err.log
sudo grep -i 'systemctl\|sudo\|update' /var/log/turbopanel/daemon.log | tail -20Immediate recovery on the server:
sudo systemctl restart turbopaneldOr reconcile by re-running run.sh — see Refresh a stuck daemon.
Ansible / upgrade failures
Problem: Upgrade System or daemon orchestration fails.
Solutions:
- Check daemon logs — on managed hosts Ansible runs as the
tpuser; on co-located dev it runs as the current dev user - Dirty git checkouts block upgrade; commit or stash changes in
$HOMEsource repos (~/turbopaneld,~/turbopanel,~/ui,~/website)
General debugging
| Task | Command |
|---|---|
| Instance health (HTTPS) | curl -k https://localhost:8443/api/health (omit -k when TURBOPANEL_TLS_PUBLIC) |
| Instance health (dev HTTP) | curl http://localhost:8880/api/health — co-located dev only; requires TURBOPANEL_DEV_HTTP_CONTROL_PLANE=1 |
| Client status (HTTPS) | curl -k https://localhost:8443/api/client/v1/status (omit -k when TURBOPANEL_TLS_PUBLIC) |
| Client status (dev HTTP) | curl http://localhost:8880/api/client/v1/status — co-located dev only |
| Service status | systemctl status turbopanel-instance turbopanel-caddy turbopaneld |
| Follow logs | journalctl -u turbopanel-instance -u turbopanel-caddy -u turbopaneld -f |
| Daemon WS path (HTTPS) | wss://<host>:8443/ws/daemon/v1 by default (wss://<hostname>/ws/daemon/v1 in lets_encrypt) |
| Daemon WS path (dev HTTP) | ws://<host>:8880/ws/daemon/v1 — co-located dev only |
Related documentation
- Security — TLS and authentication
- Control plane — Service layout
- Daemon setup — Node installer
- Dev console troubleshooting — Local dev issues
Last updated on