Skip to content

Logs

Scope: Caddy's own logging (always on) and per-site access logs (opt-in) — what they look like and where they go.

The server log: JSON, to stderr

Caddy logs its own life — startups, certificate events, proxy errors — as one JSON object per line on stderr. From the lab's journal:

{"level":"error","ts":1792194958.2938142,"msg":"loading initial config: loading new config: http app module: start: listening on :8091: listen tcp :8091: bind: address already in use"}

Under systemd, stderr is the journal, so journalctl -u caddy collects all of it (the systemd guide's model, again). Fields are consistent: level, ts, msg, plus structured extras like error and logger. JSON annoys some humans at first glance; it pays off the first time you filter a week of logs with jq instead of regex archaeology. (caddy run in a terminal shows the same JSON. There is no "pretty mode" switch — piping through jq or a log viewer is the idiom.)

Level discipline: certificate issuance, renewal, and reload outcomes all appear here. A Caddy you can't see the logs of is a Caddy whose certificate expiry you'll learn about from your visitors.

Access logs: opt-in, per site (documented)

Request logging is off until a site asks:

example.org {
    log {
        output file /var/log/caddy/access.log
        format json
    }
    reverse_proxy 127.0.0.1:8080
}

log inside a site block logs that site's requests — method, path, status, size, duration, the visitor's address as Caddy saw it. Formats: json (structured, the default recommendation), console (human-readable), and single_field for piping elsewhere. Output can be a file (with size/time-based rolling built in), stdout, or the systemd journal via output stderr conventions in service setups.

(Documented: the lab verified Caddy's server/error logging and its journal trail directly; per-site access log blocks follow Caddy's documented format.)

Reading a proxy failure in the log

The 502 from the proxying chapter leaves a line with "logger":"http.log.error" (or in the server log) naming the upstream and the refusal — dial tcp 127.0.0.1:8080: connect: connection refused is the signature of "app is down." Learn to tell its two neighbors apart: a timeout dialing means the app (or the network to it) is wedged, and no error at all means Caddy relayed the app's own response — the failure, if any, is inside the app and inside its logs, not Caddy's.