Skip to content

Troubleshooting

Scope: the failures you'll actually meet, symptom → fix. Messages are verbatim from real runs (Caddy 2.11.7) unless marked (documented).

"unrecognized directive: bogus_directive"

adapting config using caddyfile: Caddyfile.bad:2: unrecognized directive: bogus_directive

Symptom: Caddy won't start or reload; caddy validate reports the same.

Fix: a directive name Caddy doesn't know — a typo (file_sever), a plugin directive in a Caddy built without that plugin, or a directive in the wrong place (not everything is legal inside a site block; tls, for instance, has both global and per-site forms). The error names the file and line; start there.

"syntax error: unexpected token ..., expecting '}'"

syntax error: unexpected token '200', expecting '}', at Caddyfile.unclosed:2

Symptom: validate/adapt fails on structure.

Fix: a brace problem — a missing close, or a block opened on a line the parser reads as a directive with arguments. Caddyfile blocks must open { at the end of the site/directive line. caddy fmt on the file often makes the structural slip visible in its diff.

"bind: address already in use"

loading initial config: ... listening on :8091: listen tcp :8091: bind: address already in use

Symptom: startup dies with a listen error in the JSON log.

Fix: something already owns the port — usually a previous Caddy you forgot was running (check systemctl status caddy / ss/lsof for the owner), or another server entirely. Note the mirror-image trap: two Caddies can share a port on Linux in some setups, which hides the duplicate until behavior gets weird. One Caddy per machine is the sane rule.

502 Bad Gateway

Symptom: visitors get 502 Bad Gateway with Server: Caddy.

Fix: Caddy is healthy; the upstream isn't answering. Confirm the app listens where the config points (curl 127.0.0.1:8080 from the Caddy machine), then read Caddy's log for the dial error: connection refused (app down), timeout (app wedged or a firewall), TLS errors (you proxied to an HTTPS port with an HTTP upstream line — reverse_proxy speaks plain HTTP to upstreams unless the upstream is named with https://). Full discussion in Reverse proxying.

Certificate issuance fails (documented)

Symptom: a public site stays unreachable over HTTPS, and the server log shows ACME errors — challenge timeouts, "connection" refusals from the CA, or rate limit notices.

Fix, in order: DNS actually points at this machine (check from outside, not from the machine itself — split-brain DNS lies); ports 80 and 443 are reachable from the internet (router NAT, cloud firewall, ISP blocking inbound 80 is a real thing on residential connections); nothing else already squats 80/443. Fix the cause, restart, and Caddy retries. If you hit a Let's Encrypt rate limit by flapping, stop flapping: the limit window is hours-to-days, and the staging endpoint exists for exactly this testing phase.

"It serves the wrong site"

Symptom: a name you configured shows a different site, or an unconfigured name shows a real one.

Fix: site selection is by the request's Host (and SNI for TLS). Overlapping blocks — a :443 catch-all plus named sites, or a wildcard — match in Caddy's documented specificity order, not file order. List what the running instance thinks it has: the admin API's /config/ (or caddy adapt on the file) shows the compiled truth. (Documented mechanics; the API itself was verified in the Running Caddy chapter.)

Internal-CA certificates rejected by other devices

Symptom: localhost HTTPS works on the Caddy machine, but your phone/laptop shows a trust warning for the LAN site.

Fix: expected — those devices don't know Caddy's private CA. Install its root certificate (~/.local/share/caddy/pki/authorities/local/root.crt, or /var/lib/caddy/... for the service install) into each device's trust store, or accept the warning for LAN-only tools. Do not "fix" this by disabling verification in anything that matters.