Caddy logging: JSON output, field filters, and sensitive data
Enable Caddy access logs, configure JSON and rotation, filter headers and query parameters, mask addresses, and validate field mapping before collecting in Guance.
Caddy has structured runtime logging, while HTTP access logging must be enabled for a site with the log directive. Its default encoding depends on the output environment: an interactive terminal can receive console output, while non-terminal output uses JSON. Set format json explicitly when a collector expects JSON.
The main configuration decisions are destination, schema, and fields to remove before emission. This guide covers access logs, rotation, JSON formatting, field filters, query-string handling, and collection in Guance. Examples target Caddy 2; rotation syntax below follows the current 2.11 documentation.
Enable access logging
A single directive enables access logs for a site:
example.com {
log
}
The default destination is stderr. The site still needs its actual handler, such as file_server or reverse_proxy, to serve useful content; the minimal example only demonstrates logging configuration.
For file output, configure a writable destination and bounded rotation:
example.com {
log {
output file /var/log/caddy/access.log {
roll_size 10MiB
roll_keep 5
roll_keep_for 336h
}
format json
}
}
This example rotates by size and limits retained backup files by count and age; 336h is fourteen days. It does not promise fourteen complete days of history if the count limit removes files sooner. Caddy 2.11 uses roll_keep_for; older configurations may use roll_keep_days. Validate the syntax against the installed version before changing it.
Available output modules include stdout, stderr, file, net, and discard. discard suppresses output for that logger rather than disabling all Caddy logging. For containers, stdout/stderr lets the runtime and collector manage forwarding and local retention. On a host, built-in file rotation can replace a separate rotation job for that file.
The file writer has lifecycle details: changes to settings for an already-open file may require a restart, or a new filename, rather than taking effect on a configuration reload. Verify this before relying on an updated retention setting.
Set the encoding and field names
JSON is convenient for machine processing; console is easier to read interactively while retaining structured details. Place the following log block inside your site block to choose explicit names and units:
log {
output stdout
format json {
message_key msg
level_key severity
time_key timestamp
time_format rfc3339_nano
level_format upper
duration_format ms
}
}
This emits a timestamp with a time-zone offset and uses milliseconds for duration encoding. With the default JSON encoder, ts is floating-point seconds since the Unix epoch and durations are encoded in seconds. Custom settings change that contract, so record the units with your collection rules.
Core encoder fields such as timestamp and level are configured through these encoder options. They are not ordinary fields that the filter encoder can freely remove or rename.
Remove fields before they leave Caddy
A format filter block applies operations to particular field paths. The > character descends into nested objects. These are alternative examples to place inside a site's log block; combine the required rules into one filter configuration.
Delete unwanted fields
format filter {
request>headers>Authorization delete
resp_headers>Server delete
wrap json
}
These rules remove the named fields from log output. They do not change the request passed to an upstream server or the response returned to the client. List each field explicitly; do not assume a wildcard such as Sec-* matches a header family.
Caddy redacts Cookie, Set-Cookie, Authorization, and Proxy-Authorization header values by default in access logs. The log_credentials server option can disable that protection. Keep the default unless an exceptional requirement has been carefully addressed, and check both request and response samples. Other custom headers, URLs, and application fields are not automatically safe.
Rename a field without changing its value
format filter {
request>uri rename path
status rename http_status_code
wrap json
}
The URI remains nested under request after renaming; it is not promoted to the top level. It can still contain a query string, so calling it path does not remove query parameters. The top-level status here is an HTTP response code, not log severity.
Replace values or use a targeted regular expression
format filter {
user_id replace REDACTED
request>uri regexp "apikey=[A-Za-z0-9]+" "apikey=REDACTED"
wrap json
}
The user_id rule applies only if that field is actually present. The regular expression demonstrates the RE2-based replacement mechanism; it covers only the stated character pattern. It is not a complete secret detector and may leave a suffix of a token containing punctuation or miss differently encoded data. Prefer the query-aware filter below for named URL parameters.
Mask all relevant address fields
format filter {
request>remote_ip ip_mask {
ipv4 24
ipv6 56
}
request>client_ip ip_mask {
ipv4 24
ipv6 56
}
request>headers>X-Forwarded-For delete
wrap json
}
remote_ip is the directly connected peer. client_ip can represent the original client when trusted-proxy handling is configured. Masking only one can leave the same address elsewhere in the event. Audit additional proxy headers and custom fields too.
For stable, limited correlation, a field can instead use the hash filter, for example request>client_ip hash. Caddy's filter retains only the first four bytes of SHA-256, represented as eight hex characters. That is a short, unsalted hash: collisions and guessing are possible, especially for a small address space. It is not anonymization or a reliable unique visitor ID. Neither hashing nor subnet masking automatically removes privacy obligations.
Handle query parameters by name
format filter {
request>uri query {
delete apikey
replace secret_code REDACTED
hash email_address
}
wrap json
}
The filter removes apikey, replaces the value of secret_code, and hashes email_address while retaining other parts of the URI. The hash has the same short-hash limitations described above. Parameter names must match the actual input; test repeated parameters, encoding, and the names your application accepts.
Filtering a URL in one log field does not remove a secret from a Referer header or a different custom field. Keep sensitive credentials out of URLs in the application design as well.
Validate with a local request
This complete local example enables explicit JSON access logs and removes an API key query parameter:
{
admin off
auto_https off
}
http://127.0.0.1:8088 {
log {
output stdout
format filter {
request>uri query {
delete apikey
}
wrap json
}
}
respond "ok" 200
}
Save it as Caddyfile, validate it, then run it in a foreground terminal:
caddy validate --config ./Caddyfile --adapter caddyfile
caddy run --config ./Caddyfile --adapter caddyfile
From a second terminal, send a request containing deliberately fake test values:
curl 'http://127.0.0.1:8088/test?apikey=fake-secret&item=1' \
-H 'Authorization: Bearer fake-token'
The response should be ok. Inspect the access event: item=1 should remain, apikey should be absent, and the authorization value should be redacted. Runtime events may still appear on stderr. Test a failed response with your real handler as well, then confirm that sensitive values do not survive in another field.
What if an existing system requires text logs?
A third-party transform encoder can provide formats such as Common Log Format, using a custom Caddy build with that module installed. format transform "{common_log}" is not available merely because stock Caddy is installed. Check the module's documentation and compatibility before using it.
Keep JSON where your collection pipeline supports it, or choose a text format for a specific compatibility requirement. Structured output simplifies field extraction, but text logs can still be parsed; format alone does not determine whether analysis is possible.
Collect Caddy logs in Guance
For a file deployment, the DataKit log collector can read the chosen access-log path. Check permissions and rotated-file handling, then configure source, service, and processing. For containers, use the existing stdout/stderr collection route and avoid collecting a duplicated file copy.
Use the Pipeline quick start to validate JSON extraction and field mapping. Preserve the HTTP response code as http_status_code; map Caddy's level or renamed severity to the log status. Match the timestamp parser to ts or the configured custom field and unit. Do the same for durations.
Send a successful request and a deliberately failed request through the actual site. Verify timestamp, URI, HTTP code, severity, and duration before creating error or latency views. Caddy's output filters and DataKit's parsing serve different stages of this process; one does not replace the other.
Frequently asked questions
Why are there no access logs in my Caddy container?
Access logging is separate from runtime logging and must be enabled for the site. Check that the correct configuration is loaded, the request reaches that site, and the destination is stdout/stderr if you expect to see it through docker logs. Set format json explicitly if required by the collector.
Can one filter delete a whole header family?
Do not rely on wildcard matching in field paths. Enumerate the relevant fields and use test requests containing fake sensitive values to confirm all required deletions.
Should we hash or mask IP addresses?
Masking can retain a network prefix for coarse analysis. Hashing can show repeated values, subject to collisions and guessing risk; it cannot establish a unique person or safely anonymize an address. Choose based on the actual analysis need and inspect every address-bearing field.
Does JSON eliminate the need to check Pipeline parsing?
No. JSON defines a structure, but timestamps, duration units, HTTP codes, and logging levels still need correct interpretation. Validate a real event end to end.
References and related reading
The primary reference is Caddy's log directive documentation. See the transform encoder module for its separate installation and syntax.
Continue with the Nginx logging guide. The related log formatting guide is currently in Chinese.
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan