SadServers
  • Scenarios
  • Labs
    All Labs Linux & Bash Web Servers Databases Data Processing Docker Kubernetes CI/CD Infrastructure as Code Tooling / Applications
  • Dashboard
  • Solutions
    For Individuals For Businesses
  • Ranking
  • Newsletter
  • Documentation
    FAQ Support Pro Accounts Pro+ Accounts Business Accounts Gift API CLI/TUI Privacy Troubleshooting Interviews
  • Blog
  • Pricing
  • Gift
    Gift Purchase Gift Redeem
  • About
Log In - Sign Up
  1. Labs
  2. Caddy
  3. Troubleshooting

Guide

Concepts and learning path

Troubleshooting

Failure modes and fixes

Cheatsheet

Commands to keep handy

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 caddy or caddy 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_path stripping prefixes unexpectedly
  • try_files is not behaving as expected
  • root is 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/Caddyfile

2. 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 -f

4. Test upstream and public endpoint

curl -v http://127.0.0.1:8000/health curl -vI https://example.com

Permission 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/caddy

Practice scenarios

Hands-on Caddy scenarios on live Linux VMs: caddy

Cheatsheet →
SadServersSadServers

Real-world Linux and DevOps scenarios for hands-on learning and technical assessment.

Uptime Robot ratio (30 days)
Product
  • Scenarios
  • For Individuals
  • For Businesses
  • Pricing
Resources
  • FAQ
  • Blog
  • Newsletter
Company
  • About Us
  • Support
  • Privacy Policy
  • Terms of Service
  • Contact
Connect With Us
info@sadservers.com

Made in Canada 🇨🇦
Updated: 2026-06-13 16:06 UTC – 2d2950a