Operate the proxy
Enable structured access logs
Record requests with useful identity, status, duration, size, and upstream evidence without logging secrets.
10 minute lesson
When a user says “the site is slow” or “I got an error”, the proxy is the best witness you have. It saw the client request, the routing decision, the upstream answer, and the timing of all three. Access logs connect client symptoms with proxy decisions — but only if you can query them.
That’s the case for structure. A JSON log line is a record with named fields, not a sentence to parse with regexes. Structured logs are easier to filter and correlate with backend events.
Enable a local log
http://127.0.0.1:8080 {
log {
output file access.json
format json
}
reverse_proxy 127.0.0.1:4001
}
The log directive enables access logging for this site. output file writes to a file instead of stderr, and format json emits one JSON object per request.
Generate evidence worth reading
Make successful and failed requests, then find status, duration, request ID if present, and upstream failure details:
curl http://127.0.0.1:8080/hello
# stop the backend, then:
curl http://127.0.0.1:8080/hello
Now read the log with jq:
jq '{status, duration, uri}' access.json
# {"status": 200, "duration": 0.003, "uri": "/hello"}
# {"status": 502, "duration": 0.001, "uri": "/hello"}
The success line tells you the path, the outcome, and how long the whole exchange took. The failure line is more interesting: status 502 with a tiny duration means the upstream connection failed immediately — a refused connection, not a slow backend. A 502 after several seconds points at timeouts instead. You just diagnosed two different failure modes from two numbers.
Try a filter you’d actually run during an incident:
jq 'select(.status >= 500)' access.json
One line, every server-side failure. That query is the reason format json earns its place.
Log carefully
Access logs are a data liability as much as a debugging tool. They capture headers, and headers carry credentials. Redact authorization, cookies, sensitive query values, and private bodies — Caddy’s log directive accepts filters that replace or delete named fields before they’re written, so tokens never land on disk in the first place.
Then treat the file itself as sensitive. Restrict log access and retention: tighten file permissions, and rotate with a defined lifetime. A year of forgotten access logs is a breach waiting for its disclosure date.
Lesson completed