-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Description
Some background
I'm using NixOS as my server OS with Caddy serving my blog and other sites. I've set it up so that it is serving the files directly from the Nix store. What is important to know here is that when building with Nix it strips all timestamps from files, so no modification times are present (found a somewhat related issue). So since Caddy can't use modification times to respond to If-Modified-Since
header, since files are seemingly never modified, it always responds with 304 Not Modified
, breaking caching for my blog. So far Caddy is behaving as expected.
So I've modified my Caddyfile to force Caddy to use ETags with the following config:
luka.korosec.cc {
header {
-Last-Modified
}
file_server {
etag_file_extensions .etag
}
root * /nix/store/3gsfndbnp4v4z0k7cs12b82vqvvpqxr7-tech-blog-1.0.0
encode gzip
}
I've removed the Last-Modified
header (so browsers can't use If-Modified-Since
header), and added etag_file_extensions
. I generate the ETag files during Nix build and Caddy correctly uses them as etag
header. Up till now, I can't fault Caddy for anything, but what follows is a Caddy bug I think.
The bug
Here is what I think shows a bug:
- Make initial request:
curl -v https://luka.korosec.cc
- Note the ETag in response:
etag: b79e32c68bbcbbf5599c452748e903f6
- Make conditional request:
curl -v -H "If-None-Match: b79e32c68bbcbbf5599c452748e903f6" https://luka.korosec.cc
- Observe that server returns
200 OK
instead of304 Not Modified
curl session
β― curl -v https://luka.korosec.cc
[...]
> GET / HTTP/2
> Host: luka.korosec.cc
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/2 200
< accept-ranges: bytes
< alt-svc: h3=":443"; ma=2592000
< content-type: text/html; charset=utf-8
< etag: b79e32c68bbcbbf5599c452748e903f6
< server: Caddy
< vary: Accept-Encoding
< content-length: 1485
< date: Fri, 27 Jun 2025 19:07:19 GMT
<
<!DOCTYPE html>
[...]
β― curl -v -H "If-None-Match: b79e32c68bbcbbf5599c452748e903f6" https://luka.korosec.cc
[...]
> GET / HTTP/2
> Host: luka.korosec.cc
> User-Agent: curl/8.7.1
> Accept: */*
> If-None-Match: b79e32c68bbcbbf5599c452748e903f6
>
* Request completely sent off
< HTTP/2 200
< accept-ranges: bytes
< alt-svc: h3=":443"; ma=2592000
< content-type: text/html; charset=utf-8
< etag: b79e32c68bbcbbf5599c452748e903f6
< server: Caddy
< vary: Accept-Encoding
< content-length: 1485
< date: Fri, 27 Jun 2025 19:09:07 GMT
<
<!DOCTYPE html>
[...]
root directory files
β― ls -lah /nix/store/3gsfndbnp4v4z0k7cs12b82vqvvpqxr7-tech-blog-1.0.0
dr-xr-xr-x root root 82 B 1970-01-01 01:00:01 π ./
drwxrwxr-t root nixbld 11 MB 2025-06-27 05:08:40 π ../
dr-xr-xr-x root root 164 B 1970-01-01 01:00:01 π css/
dr-xr-xr-x root root 462 B 1970-01-01 01:00:01 π fonts/
dr-xr-xr-x root root 172 B 1970-01-01 01:00:01 π img/
dr-xr-xr-x root root 8 B 1970-01-01 01:00:01 π posts/
.r--r--r-- root root 1.5 KB 1970-01-01 01:00:01 π index.html
.r--r--r-- root root 33 B 1970-01-01 01:00:01 π index.html.etag
β― cat /nix/store/3gsfndbnp4v4z0k7cs12b82vqvvpqxr7-tech-blog-1.0.0/index.html.etag
b79e32c68bbcbbf5599c452748e903f6
What should happen
When the If-None-Match
header matches the current ETag, the server should return 304 Not Modified
with no response body.
I'm running Caddy version 2.10.0.