Caddy is not a line-for-line Nginx replacement. Identify the matcher, handler, and response behavior in each Nginx block, then express those things in Caddy directive order.
Translate location blocks
# Nginx
location /api/ { proxy_pass http://backend:8080/; }
# Caddy
handle /api/* {
uri strip_prefix /api
reverse_proxy backend:8080
}A prefix location becomes a path matcher. A broad location / is usually a fallback handler. Keep narrow routes explicit.
proxy_pass and path prefixes
In Nginx, the trailing slash controls whether the matched location prefix is replaced. Caddy forwards the request path by default, so use uri strip_prefix when the upstream expects a different path.
Redirects and headers
# Nginx
return 301 https://$host$request_uri;
proxy_set_header Host $host;
add_header Cache-Control "public, max-age=3600";
# Caddy
redir https://{host}{uri} permanent
reverse_proxy app:3000 {
header_up Host {host}
}
// phpcs:enable Generic.Files.LineLength.TooLong
header Cache-Control "public, max-age=3600"header_up changes what the application receives; header changes what the browser receives. Check both with a real request.
Migration checklist
- Run
caddy validate --config Caddyfile. - Test prefixed routes and upstream URIs.
- Compare redirects and query strings.
- Inspect response and upstream request headers.
- Test missing files, slash variants, OPTIONS, and 404s.