Log_skip directive has no effect?

1. The problem I’m having:

I am trying to use log_skip to suppress logging of requests originating from specific public IP addresses, but the directive seems to have no effect (i.e., requets from those IPs are still logged). I know I could filter the logs after the fact, but I really want to not log these in the first place.

2. Error messages and/or full log output:

No error messages per se, but request handling logs are still emitted even though log_skip should have stopped them. An example of such a log (where all addresses and FQDNs have been anonymized):

{"level":"info","ts":1775811061.6357963,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"198.18.0.1","remote_port":"40986","client_ip":"192.18.0.1","proto":"HTTP/1.1","method":"GET","host":"example.com","uri":"/ping.php","headers":{"User-Agent":["curl/7.88.1"],"Accept":["*/*"]}},"bytes_read":0,"user_id":"","duration":0.000325783,"size":0,"status":308,"resp_headers":{"Server":["Caddy"],"Connection":["close"],"Location":["https://example.com/ping.php"],"Content-Type":[]}}

3. Caddy version:

v2.11.2 h1:iOlpsSiSKqEW+SIXrcZsZ/NO74SzB/ycqqvAIEfIm64=

4. How I installed and ran Caddy:

Installed from the Debian package then replaced /usr/bin/caddy with a custom one built with xcaddy (as the online build server was non-functional at that time) including modules caddy-l4 and caddy-events-exec (neither of which is used in this specific case).
Run through systemd, reconfigured through commande “caddy --config /etc/caddy/Caddyfile reload.”

a. System environment:

Debian Trixie 13.4 on an ARM64 machine

b. Command:

N/A (caddy is run by Debian through systemd)

c. Service/unit/compose file:

N/A (service file is the one provided by the Debian package)

d. My complete Caddy config:

NOTE: despite the request to not redact addresses and domains, I cannot disclose those, and have replaced them with values suitable for documentation.

{
	default_bind 198.18.1.0:443 [2001:0002::1:0]:443
	log {
		output file /var/log/caddy/access.log {
			mode 0666
			roll_interval 24h
			roll_at 00:00
		}
		format json {
		}
		include http.log.access
	}
	# auto_https disable_redirects
	email x@example.com
	cert_issuer acme
}

example.com {
	log
	@our_own_ips remote_ip 198.18.0.1/32 2001:0002::1/128
	log_skip @our_own_ips
	reverse_proxy 198.18.2.0:443 {
		transport http {
			tls
			tls_server_name example.com
			proxy_protocol v2
			keepalive off
		}
	}
}

subdomain2.example.com {
	log
	redir https://example.com/subdomain2{uri} 308
}

subdomain1.example.com subdomain3.example.com {
	respond "OK" 200
}

5. Links to relevant resources:

Weird, that should work. I don’t see any obvious problem with your config. Try removing the matcher from log_skip or matching on some paths, does that work? If so then for some reason the remote_ip matcher is not matching the IPs you think it should. Otherwise, there’s maybe a bug that will need to be tracked down.

I’ve tried with path_regexp instead of remote_ip, with the same (lack of) effect.

I also checked that the remote_ip in the config matched the remote_ip (and client_ip) field in the log line which I wanted skipped but was not:

# grep -F 198.18.0.1 -m 1 /etc/caddy/Caddyfile <(tac /var/log/caddy/access.log | grep -F 198.18.0.1 -m 1)
/etc/caddy/Caddyfile:	@our_own_ip_addresses remote_ip 198.18.0.1/32 2001:0002::1/128
/dev/fd/63:{"level":"info","ts":1775896741.6245382,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"198.18.0.1","remote_port":"33312","client_ip":"198.18.0.1","proto":"HTTP/1.1","method":"GET","host":"example.com","uri":"/ping.php","headers":{"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}},"bytes_read":0,"user_id":"","duration":0.000155455,"size":0,"status":308,"resp_headers":{"Connection":["close"],"Location":["https://example.com/ping.php"],"Content-Type":[],"Server":["Caddy"]}}
# _

Time for debugging, then, I guess.

Added level debug and commented out the include line in the log block (thus logging everything) then reloaded the config. I could see the debug log was enabled (there are “debug” level lines), but the “ping” requests do not cause any debug logs.

What can I do next?

Oh sorry, I wasn’t looking carefully enough. The logs you’re seeing are HTTP->HTTPS redirects, so they’re not going through your handlers and it’s not hitting log_skip because the handlers are only for the HTTPS server.

I think the problem is this line:

This is causing all access logs to be grabbed, ignoring whether log is enabled in a site or not. Remove that line and it should only log the stuff you want.

I’ve commented out the include http.log.access line, but I still get the redirects in the log. Any other suggestion?

Are you sure you actually reloaded the config? What’s in your Caddy runtime logs? What’s your entire config at this point?

I am sure I actually reloaded the config, through caddy –config /etc/caddy/Caddyfile reload which replied

2026/04/28 18:09:13.419	INFO	using config from file	{"file": "/etc/caddy/Caddyfile"}
2026/04/28 18:09:13.435	INFO	adapted config to JSON	{"adapter": "caddyfile"}

As I said, I cannot share the actual config or logs. Therefore I am building a representative config which I’ll share once I reproduce (or solve!) the issue. Therefore I made a Caddyfile which is identical to the one above except for addresses and FQDNs (I checked that by running that Caddyfile through sed to replace addresses/FQDNs, then doing a diff between that and a copy of the Caddyfile above).

I then ran the following tests, which effectively show that HTTP-to-HTTPS is always logged, and HTTPS is logged except for the address mentioned in log_skip :

  • querying https://example.com from 198.18.0.1: not logged (as intended)
  • querying https://example.com from 198.18.0.2: logged (as intended)
  • querying http://example.com from 198.18.0.1: logged (log_skip not applied)
  • querying http://example.com from 198.18.0.2: logged (log_skip not applied)

[EDIT: I’d written the test results wrong initially. I re-ran the tests and fixed this post]

Then I commented out the include http.log.access line and ran the same four tests, which gave the same results.

So it would seem that:

  1. the include line does not matter (as far as these four tests go; of course, without the include line, the whole log contains many more entries, but those are not my concern here), but also
  2. HTTP-to-HTTPS log lines cannot be filtered out?

Well anyway, I’ll need a minimally reproducing config because I can’t reproduce it myself.

Well the minlmally reproducing config is the one you’re seeing, except I mapped original addresses to example ones – as you will have to do too anyway in order to reproduce the issue…

I can’t do it until next week, but here’s wha I can do: I’ll try and set up containers using the exact addresses I’ve given and mimicking Let’s Encrypt with Step/Step CA and if need be a local DNS. If that reproduces the issue then I will provide the compose.yaml file.

The point of a minimally reproducing config is that you remove everything that doesn’t matter, use generic names, remove routes that don’t matter etc, and test every step of the way confirming the unwanted behavior still remains.

The only config you gave me, I found to be fixed by removing the log include line. You haven’t actually shown any proof of the problem still existing without that at this stage.

Show your steps to reproduce it. Use curl -v commands to make reproducible requests to the server to trigger the behavior.

Ok. I’m away from my setup right now but as soon as I can I’lll post the curl -v commands and corresponding logs (all anonymized using th same sed filter I used).