Just had a chat with @matt. Apart from discovering what looks like a weird bug (which is adjacent to your issue), to explain what’s going on, allow me to elaborate on the intended behaviour:
When all three are lined up like this:
header * Cache-Control max-age=3600
header /static/* Cache-Control max-age=31536000
header /favicon.ico Cache-Control max-age=31536000
The Caddyfile adapter interprets it like this:
"routes": [
{
"match": [
{
"path": [
"/static/*"
]
}
],
"handle": [
{
"handler": "headers",
"response": {
"set": {
"Cache-Control": [
"max-age=31536000"
]
}
}
}
]
},
{
"match": [
{
"path": [
"/favicon.ico"
]
}
],
"handle": [
{
"handler": "headers",
"response": {
"set": {
"Cache-Control": [
"max-age=31536000"
]
}
}
}
]
},
{
"handle": [
{
"handler": "headers",
"response": {
"set": {
"Cache-Control": [
"max-age=3600"
]
}
}
}
]
}
]
Note that the smallest Cache-Control
setting - the generic one (i.e. header *
) - comes last. This is because Caddy is designed to order adjacent routes, whenever they are based on path matchers, by the specificity of the path matched.
In plain English: longest matching path executes first. Side effect - shortest matching path executes last. When dealing with headers, when the headers are written, the most recent call to set a header is the only one that matters. So if we execute the most generic header
last, the most generic header is the one that will override the others. Obviously this isn’t ideal behaviour.
(You may or may not have noticed how in the above JSON, /static/*
comes before /favicon.ico
- despite how I just said Caddy’s designed to sort longest path routes first. Yeah, that’s the bug I mentioned earlier. It’s doing some sorting - *
always comes last - but something’s whacky there. Strictly speaking, though, this bug isn’t what’s causing your issue, the actual intended design of Caddy is doing that. Matt also found the cause of that bug and will fix it…)
Anyway, putting the header
directives in a handle
block forces the Caddyfile adapter not to put it before the generic one.
Putting them in a route
block - which is designed to give you explicit control over the execution of the directives - will also allow you to get the desired behaviour, by manually ordering the headers from generic to specific.
So here’s the latest I believe should work:
solovyov.net {
route {
header Cache-Control max-age=3600
header /static/* Cache-Control max-age=31536000
header /favicon.ico Cache-Control max-age=31536000
}
handle /q/* {
uri strip_prefix /q
root * /mnt/share
file_server browse
}
encode zstd gzip
root * /opt/solovyov.net/www
file_server
}
Edit: @matt moves fast - above mentioned bug fixed in httpcaddyfile: Fix route ordering bug · caddyserver/caddy@cd9317e · GitHub, in case you’re curious.