Caddy Operations Manual
On this page
Diagnose a request through ingress, matching, path rewriting and upstream handling. A parseable Caddyfile, loaded configuration and correct public response are separate results to verify.
| Task | Start here |
|---|---|
| Proxy 404 / wrong path | Routing and prefixes |
| Apply configuration | Validate, reload, verify |
| TLS / client certificates | Certificate ownership and mTLS |
The upstream may receive a rewritten path
handle /api/* preserves the path; handle_path strips /api. Bare /api does not match /api/* and reaches this example’s fallback.
Complete site example: static files and /api/* routing. Merge the site block into the real Caddyfile and adapt domain, root, and upstream.
example.com {
handle_path /api/* {
reverse_proxy 127.0.0.1:3000
}
handle {
root * /srv/www
file_server
}
log
}
Here /api/users reaches the upstream as /users. Use handle /api/* if the application expects the prefix. /api/* does not match bare /api; handle it deliberately. Handle groups are mutually exclusive with a fallback. file_server does not enable directory listing unless browse is specified.
Reach the upstream from Caddy’s environment first
# Request the rewritten upstream path from the Caddy host
In a container, loopback refers to that container; test the actual address in the same network namespace. Verify status/body before testing the proxy.
# Test the public path and TLS through the selected entry
--resolve specifies name/port/IP. Compare the same resource on both paths and also test /api/, bare /api, and static paths.
# Inspect Caddy diagnostics near the request time
Distinguish runtime from access logs; configured access logs may be files. Use upstream errors behind 502 to identify protocol/network issues.
Compare the same resource after rewriting. --resolve preserves the URL hostname and SNI while selecting an ingress IP; a Host header alone does not change SNI. Do not use -k to hide a TLS failure.
| Symptom | Inspect |
|---|---|
| 502 | Upstream address/protocol/namespace and dial/TLS errors; test from Caddy’s environment |
| 404 | Which layer returned it, matcher, prefix stripping, static root and permissions |
| 403 | File access, auth and upstream logs; status code alone does not locate the owner |
For multiple backends, use a health endpoint that represents readiness to receive requests. A successful probe is not proof of every dependency; several ports on one host do not provide host-level redundancy.
Alternative site: proxy the entire site to multiple backends. Replace the earlier same-domain block rather than append a duplicate; retain a matcher if only /api/* should be proxied.
example.com {
reverse_proxy 127.0.0.1:3000 127.0.0.1:3001 {
lb_policy round_robin
health_uri /health
health_interval 10s
}
}
Adapt, validate and reload the actual service
Identify binary, config path, and service identity
# Check the version for the intended configuration syntax
Check plugins with caddy list-modules; installing a binary does not prove the service uses it.
# Inspect ExecStart, ExecReload, User, and config location
Substitute the actual path and retain the previous file before editing. The configuration examples are separate alternatives, not duplicate same-domain blocks to concatenate.
# Preview formatting changes without writing
Apply --overwrite separately if desired after review. Formatting is not semantic validation.
# Inspect the JSON adapted from the Caddyfile
Inspect route order, matchers, and rewrites. Adapt does not start listeners and can expose sensitive configuration.
# Load and validate modules without replacing the running service
Use the actual service identity/environment where possible to check access. Fix failures before reload.
# Apply through the verified ExecReload action
Requires a reload-capable unit targeting this file. Check status/logs and whether the previous config still serves after rejection, rather than immediately restarting.
# Check service state, then repeat real route requests
Verify API, bare-prefix, static, and health paths. To roll back, restore the old file, validate/reload, and repeat the same requests.
fmt formats; adapt produces JSON; validate provisions modules but does not test all live dependencies. Check file access as the service identity. Verify ExecReload targets the intended file. For custom units use the configured caddy reload path. Keep the prior config and inspect which version remains active on failure.
Reload supports config switching, not unconditional continuity for every long-lived stream. Test WebSocket/stream close policies. GET /config on the admin API shows runtime JSON but can contain secrets; keep its access scoped. There is no general standard list-certificates CLI command; inspect served TLS and configured storage.
Give certificates one maintenance owner
Automatic HTTPS needs correct DNS, challenge reachability or DNS-plugin permissions, and persistent writable certificate storage. Issuers are configurable, not only Let’s Encrypt. Manual certificate files need an external renewal/deployment chain; see certificates. HTTP/3 also needs UDP ingress and client support.
Independent mTLS site: the client CA file must exist and be readable by the service. Deploy using the validation/reload workflow above.
internal.example.com {
tls {
client_auth {
mode require_and_verify
trust_pool file /etc/caddy/client-ca.pem
}
}
reverse_proxy 127.0.0.1:3000
}
Use a version supporting trust_pool. client-ca.pem is a trusted client-issuing CA, not a revocation list. mTLS certificate authentication does not replace application authorization. reverse_proxy already handles common forwarding headers; configure trusted_proxies for known ingress rather than trusting arbitrary X-Forwarded-For.
Test mTLS with both negative and positive requests
Run after installing the mTLS site above: test rejection without a client certificate, then test an approved client identity. Use the site’s approved trust and key files.
# Without a client certificate, verify that mTLS is required
Expect TLS rejection. A normal application response may mean a wrong entry or missing client_auth. Use --cacert for a private server CA, not -k.
# Verify access using an approved client certificate
After TLS succeeds, verify application authorization; a trusted certificate is not universal business permission.
handle_path · reverse_proxy · CLI · TLS
For the actual edge ingress setup, see the WildWindStudio EdgeOne runbook.