AO

2026-10-09 · caddy · linux · systemd · logging · privacy

Caddy writes requests for a host no site names to its default log, the journal

Once a Caddyfile site has its own log, Caddy also logs requests for hosts no site names (a bare IP, no Host) to its default log, the journal under systemd, with the whole address: 8,577 lines in 15.5 days on AO's server. Measured by AO, 2026-10-09.

AO's two sites each write their access lines to their own file, and the file shortens every address to a /24 before it is written. That is what AO's privacy page promises. On 2026-10-09 we found 8,577 access lines in the systemd journal too, each with the whole address. None of them was a visit to AO.

What was in the journal. Every line came from the logger http.log.access, not from the site loggers (http.log.access.log0 and log1). Their Host was a bare IP address, an IP address with :80, nothing at all, or someone else's domain: scanners trying the server's address. There were none before the sites had logs: the journal goes back to 2026-09-14 and holds no access line until 01:06 UTC on 2026-09-24, eight minutes after Caddy first started with the sites' logs.

Why. caddy adapt shows what the Caddyfile turns into. It prints JSON and changes nothing:

printf 'example.com {\n\tlog {\n\t\toutput file /tmp/x.log\n\t}\n\trespond "hi"\n}\n' \
  | caddy adapt --adapter caddyfile --config /dev/stdin

The result, Caddy 2.11.4, the logging parts only:

"logging":{"logs":{"default":{"exclude":["http.log.access.log0"]}, "log0":{... "include":["http.log.access.log0"]}}}
"servers":{"srv0":{... "logs":{"logger_names":{"example.com":["log0"]}}}}

One site's log turns on access logging for the whole server. A request whose host is in logger_names goes to that site's logger, which the default log excludes. A request for any other host has no logger of its own. It is logged by plain http.log.access, and the default log does not exclude that one. The default log writes to stderr, and under systemd stderr is the journal.

What the docs say. The log directive page says the directive "applies to the hostnames of the site block it appears in", and says nothing about requests for other hosts. The global options page says that to exclude only HTTP access logs "you would exclude http.log.access", without saying why you would. Caddy's source comments, which its JSON docs are built from, describe the JSON side: with logging on, all requests to a server go to the default logger unless their host is mapped, or skip_unmapped_hosts is set. No page connects that to a Caddyfile site's log.

The fix AO uses. One global block, before the first site:

{
	log default {
		exclude http.log.access
	}
}

The default log then excludes every access line. Each site's own file still gets its own lines, because the file's log includes them itself. After AO's reload at 13:29 UTC the journal got no access line in the next 95 minutes, against 487 in the 24 hours before, and both files kept writing. A request for no site of ours is now written nowhere.

How to check your own server:

journalctl -u caddy --since today -o cat | grep -c '"logger":"http.log.access"'

Any count above 0 means requests for hosts no site names are reaching the journal.

Not tested. The source shows another way: a catch-all site block with no host and no log of its own makes the adapter set skip_unmapped_hosts. That is known and may work, but no proof: AO has not tried it. Other Caddy versions, and a Caddy that is not run by systemd, were not tried.

The numbers

  • AO's server, Caddy 2.11.4, two sites, each with a log to its own file that shortens the address to a /24: from 2026-09-24 01:06 to 2026-10-09 13:29 UTC the journal got 8,577 access lines, every one from the logger http.log.access (AO's server journal)
  • Their Host: 6,624 a bare IPv4 address, 1,654 an IPv4 address with :80, 162 empty, the rest names that are not AO's; not one was abstractobjective.dev (AO's server journal)
  • Before the sites had logs there were none: the journal goes back to 2026-09-14, and its first access line came at 01:06 UTC on 2026-09-24, eight minutes after Caddy first started with them (AO's server journal)
  • None of the 8,577 had the address shortened. The site's own file, which does shorten it, holds only abstractobjective.dev: 9,270 of 9,270 lines (AO's server, read 2026-10-09)
  • caddy adapt of a one-site Caddyfile whose log writes to a file: the default log excludes only http.log.access.log0, and the server maps only that site's host to log0, with no skip_unmapped_hosts (tested by AO, Caddy 2.11.4, 2026-10-09)
  • With the global option log default { exclude http.log.access } added, the default log excludes http.log.access as well (same test)
  • After that block went live, 2026-10-09 13:29 UTC: 0 access lines in the journal up to 15:04 UTC, against 487 in the 24 hours before; site.log and app.log kept getting lines, 14 and 243 (AO's server)
  • Caddy's log directive page: it "applies to the hostnames of the site block it appears in"; nothing about requests for other hosts (Caddy docs, read 2026-10-09)
  • Caddy's source comments, which its JSON docs are built from: "If enabled without customization, all requests to this server are logged to the default logger", and skip_unmapped_hosts: "requests to any host not appearing in the logger_names map will not be logged" (read 2026-10-09)
  • The Caddyfile adapter turns skip_unmapped_hosts on only for a site block with no host and no log of its own (Caddy source, httptype.go, read 2026-10-09; not tried by AO)
stored on your device — like everything here

← All knowledge · machine version (.md)