Caddy troubleshooting
Caddy won't start or reload
Run caddy validate --config /etc/caddy/Caddyfile for syntax
errors. Check journalctl -u caddy -n 50 for the exact failure.
Common causes: unclosed braces, invalid directive names, or port already
in use (ss -tlnp | grep -E ':80\|:443'). For live startup
output, use caddy run --config /etc/caddy/Caddyfile in the
foreground — see debugging workflow below.
Run caddy adapt --config /etc/caddy/Caddyfile, this is one of Caddy's most useful debugging tools because it shows the generated JSON config and often reveals routing mistakes.
Caddy API vs Caddyfile reloads
Caddy supports two configuration modes:
- Caddyfile —
systemctl reload caddyorcaddy reload - JSON admin API — e.g.
curl localhost:2019/load(default admin port)
Mixing the two is a common footgun: configure via the API, then reload from the Caddyfile — the file-based config silently overwrites whatever the API applied. Symptoms look like “my change disappeared” or routing reverted with no obvious error.
# API load (runtime JSON config)
curl -X POST "http://localhost:2019/load" \
-H "Content-Type: application/json" \
-d @config.json
# Caddyfile reload — replaces active config from disk
caddy reload --config /etc/caddy/Caddyfile
Pick one source of truth per environment. If you use the API, avoid
systemctl reload caddy unless the unit is meant to re-apply the
Caddyfile. Inspect current config:
curl localhost:2019/config/.
Certificate or TLS errors
For public ACME certificates, the hostname must resolve to this server and the selected challenge method must succeed.
Ports 80 and 443 must be reachable for ACME HTTP challenge. Check DNS
(dig +short example.com), firewall rules, and system clock
(timedatectl). Rate limits from Let's Encrypt can block
retries — read the ACME error in the journal.
Note that Caddy also supports DNS-01 challenges (via tls { dns ) which don't require public port 80 access and work for internal or wildcard certificates.
502 Bad Gateway from reverse_proxy
The upstream is down or unreachable. From the Caddy host, test directly:
curl -v http://127.0.0.1:PORT. Confirm the app listens on
the address Caddy targets (127.0.0.1 vs 0.0.0.0). Check
journalctl -u caddy for upstream connection errors.
404 on valid paths
Wrong site block matched, or file_server root does not contain
the file. Check route order — handle and route
blocks are evaluated in definition order. Verify root path and
file permissions.
More common causes:
handle_pathstripping prefixes unexpectedlytry_filesis not behaving as expectedrootis not set correctly
Redirect loop
Often caused by conflicting redir directives or a backend
that also redirects to HTTPS while Caddy already terminates TLS. Inspect
with curl -vIL https://example.com to trace each hop.
If Caddy is behind another load balancer/CDN like Cloudflare Flexible SSL or ALB/NLB TLS termination, it may be terminating TLS itself and redirecting to the backend.
Address already in use
Another process (nginx, Apache, old Caddy) holds port 80 or 443. Find it
with ss -tlnp | grep ':443 ' and stop the conflicting service
or change Caddy's bind address.
Wrong site block handling the request
Verify the request hostname matches the intended site address. Use caddy adapt and inspect logs if traffic appears to be routed to the wrong site. A
catch-all block can steal traffic:
(Note that Caddy sorts site addresses internally and Unlike Nginx or Apache, "put the specific host first" is generally not necessary.)
# Specific host first
api.example.com { ... }
# Catch-all last
:80 { ... }Debugging workflow
1. Validate config
caddy validate --config /etc/caddy/Caddyfile
caddy fmt /etc/caddy/Caddyfile2. Foreground run (caddy run vs systemd)
For startup failures, run Caddy in the foreground instead of via systemd — logs go straight to the terminal without journald buffering. Stop the service first to free ports 80/443:
systemctl stop caddy
caddy run --config /etc/caddy/Caddyfile
caddy run blocks in the foreground (ideal for debugging).
caddy start daemonizes in the background — closer to production
but harder to watch live. Use run when config fails to load or
exits immediately.
3. Check service and logs
systemctl status caddy
journalctl -u caddy -f4. Test upstream and public endpoint
curl -v http://127.0.0.1:8000/health
curl -vI https://example.comPermission errors on data directory
Caddy needs write access to store certificates and config state:
ls -la /var/lib/caddy/
chown -R caddy:caddy /var/lib/caddyPractice scenarios
Hands-on Caddy scenarios on live Linux VMs: caddy