← Back to DevBytes

Troubleshooting Caddy Web Server: Common Issues and Fixes

Troubleshooting Caddy Web Server: Common Issues and Fixes

Caddy has earned a reputation as one of the most developer-friendly web servers available today. With automatic HTTPS, a clean configuration syntax, and sensible defaults, it powers everything from small personal sites to large production deployments. However, like any infrastructure tool, Caddy can occasionally misbehave. This tutorial walks you through the most common issues developers encounter with Caddy and provides practical, tested fixes for each one.

What Is Caddy and Why Troubleshooting Matters

Caddy is an open-source web server written in Go that differentiates itself through automatic certificate management via Let's Encrypt and a human-readable configuration format called the Caddyfile. While its defaults are excellent, production environments introduce complexity: reverse proxies, TLS edge cases, permission boundaries, and DNS propagation all create opportunities for things to go wrong.

Effective troubleshooting matters because misconfigured web servers can leak traffic, break authentication flows, or serve expired certificates that immediately erode user trust. Understanding how to diagnose and resolve Caddy issues quickly reduces downtime and keeps your applications secure.

1. Caddy Fails to Start

The most frequent problem developers face is Caddy refusing to start. The root cause is almost always visible in the logs, but the error message can be cryptic if you don't know where to look.

Checking the Logs

Always start by inspecting the systemd journal or the raw log output:

journalctl -u caddy --no-pager | tail -n 50

If you're running Caddy manually in the foreground, errors print directly to stdout. Common startup failures include port conflicts, syntax errors in the Caddyfile, and permission issues.

Validating the Caddyfile

Before restarting Caddy, validate your configuration to catch syntax errors:

caddy validate --config /etc/caddy/Caddyfile

This command parses the file without attempting to bind sockets, so it's safe to run repeatedly while iterating on configuration changes.

Fixing Port Conflicts

If another process is already listening on port 80 or 443, Caddy cannot bind. Identify the offender:

sudo ss -tlnp | grep -E ':80|:443'

Either stop the conflicting service or configure Caddy to use alternative ports:

{
    http_port 8080
    https_port 8443
}

example.com {
    reverse_proxy localhost:3000
}

2. Automatic HTTPS Not Working

Caddy's automatic HTTPS is its flagship feature, but it depends on several external conditions. When certificate provisioning fails, your site will be unreachable over HTTPS.

Verifying DNS Resolution

Let's Encrypt performs HTTP-01 or TLS-ALPN-01 challenges that require your domain to resolve to the server running Caddy. Verify DNS first:

dig +short example.com
curl -v http://example.com/.well-known/acme-challenge/test

If the IP returned doesn't match your server, certificate issuance will fail. Wait for DNS propagation or fix your A/AAAA records.

Checking the ACME Storage Directory

Caddy stores certificates in a data directory, typically /var/lib/caddy/.local/share/caddy. If permissions are wrong, Caddy cannot persist certificates between restarts:

sudo chown -R caddy:caddy /var/lib/caddy
sudo chmod -R 700 /var/lib/caddy/.local/share/caddy

Switching ACME Endpoints

If you hit Let's Encrypt rate limits, switch to ZeroSSL as a fallback CA:

{
    acme_ca https://acme.zerossl.com/v2/DV90
    acme_eab {
        key_id YOUR_KEY_ID
        mac_key YOUR_MAC_KEY
    }
}

example.com {
    reverse_proxy localhost:3000
}

3. Reverse Proxy Returns 502 Bad Gateway

A 502 response means Caddy accepted the client request but could not get a valid response from the upstream backend. This is one of the most common reverse proxy issues.

Confirming the Backend Is Running

Test the upstream service directly, bypassing Caddy:

curl -v http://localhost:3000/health

If this fails, your application is down or listening on the wrong interface. Ensure your app binds to 0.0.0.0 rather than 127.0.0.1 if Caddy and the app run in separate containers.

Handling Upstream Timeouts

Slow backends cause Caddy to return 504 Gateway Timeout. Tune the transport settings:

example.com {
    reverse_proxy localhost:3000 {
        transport http {
            read_timeout 60s
            write_timeout 60s
            dial_timeout 10s
        }
    }
}

Preserving Client Headers

Many frameworks rely on headers like X-Forwarded-For and X-Forwarded-Proto. Caddy sets these automatically, but if your app expects a specific header format, you may need to customize:

example.com {
    reverse_proxy localhost:3000 {
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-Proto {scheme}
        header_up Host {host}
    }
}

4. Static Files Return 404

When serving static assets, a 404 response usually means Caddy is looking in the wrong directory or lacks read permissions.

Setting the Root Correctly

The root directive must point to the directory containing your files, not a parent directory:

example.com {
    root * /var/www/example.com/public
    file_server
}

Verify the path exists and contains the expected files:

ls -la /var/www/example.com/public/index.html

Fixing File Permissions

The Caddy user must have read access to every directory in the path. Check ownership and permissions:

sudo namei -l /var/www/example.com/public/index.html

This command prints permissions for each component in the path, making it easy to spot a restrictive parent directory.

Enabling Try Files for SPAs

Single-page applications need fallback routing so client-side routes resolve correctly:

example.com {
    root * /var/www/example.com/public
    try_files {path} /index.html
    file_server
}

5. WebSocket Connections Fail

WebSockets require special handling because they upgrade from HTTP to a persistent connection. Caddy supports WebSockets automatically through reverse_proxy, but misconfigurations can break them.

Ensuring Header Passthrough

The Upgrade and Connection headers must pass through. Caddy handles this by default, but explicit configuration helps when debugging:

example.com {
    reverse_proxy localhost:3000 {
        header_up Upgrade {http.request.header.Upgrade}
        header_up Connection {http.request.header.Connection}
    }
}

Increasing Idle Timeout

Long-lived WebSocket connections may be closed prematurely by idle timeouts. Raise the flush interval and disable buffering:

example.com {
    reverse_proxy localhost:3000 {
        flush_interval -1
    }
}

6. Performance Issues Under Load

When traffic spikes, Caddy may appear slow even though the backend is healthy. The bottleneck is often configuration rather than hardware.

Enabling Compression

Caddy does not compress responses by default. Enable gzip and zstd for text-based content:

example.com {
    encode zstd gzip
    reverse_proxy localhost:3000
}

Tuning Connection Limits

Protect your server from abusive clients with rate limiting and connection caps:

example.com {
    reverse_proxy localhost:3000 {
        lb_policy round_robin
    }
    limit {
        body 10MB
    }
}

Using the JSON Admin API for Diagnostics

Caddy exposes a local admin API that reveals runtime state. Query it to inspect active routes:

curl localhost:2019/config/ | jq

This is invaluable when your Caddyfile is complex and you need to confirm the compiled configuration matches your intent.

Best Practices for Reliable Caddy Deployments

Conclusion

Caddy is a remarkably robust web server, but its automatic features can mask underlying configuration problems until they surface at the worst possible moment. By mastering log inspection, certificate provisioning, reverse proxy tuning, and permission management, you can resolve the vast majority of Caddy issues without escalating to community forums or paid support. The key is to treat your Caddyfile as code: validate it, version it, test it in staging, and monitor its behavior in production. With these practices in place, Caddy will reward you with fast, secure, and low-maintenance web serving for years to come.

— Ad —

Google AdSense will appear here after approval

← Back to all articles