1. The problem I’m having:
I’m trying to put together a static archive of a very old CGI-based web forum. I have all the archival HTML pages, but I also need to keep inbound links working, which means a bunch of rewriting and redirection, which I tried to do with map and expression matchers, like so
map {path} {map_status} {map_location} {
#...
~^/Forum/(DCForumID[0-9]+)/?$ 308 /Forum-docs/${1}/
~^/Forum/(DCForumID[0-9]+)/(.+)$ 308 /Forum-docs/${1}/${2}
~^/Forum/[^/]+/.+$ 404 -
#...
default 200 -
}
@map_redir `{map_status} in [301, 302, 307, 308]`
@map_error `{map_status} >= 400 && {map_status} < 600`
handle @map_redir {
redir {map_location} {map_status}
}
handle @map_error {
error {map_status}
}
This doesn’t work: every path produces a 500 Internal Server Error response. The logs are not very clear, even at debug level, but what I do get strongly suggests that what’s wrong is {map_status} is being set to a string, not a number, contra the documentation:
The outputs will be type converted if possible;
trueandfalsewill be converted to boolean types, and numeric values will be converted to integer or float accordingly.
How can I fix this? I can do type conversion by hand in the named matchers if I have to, but I can’t do that in the arguments to redir and error, and error in particular seems to choke on its sole argument being a string (I get 500 responses instead of the desired 404 response).
2. Error messages and/or full log output:
With the config fragment shown above (full Caddyfile below), every request produces 500 Internal Server Error:
http requests with response logs
$ curl -v http://127.0.0.1:8080/F
* Trying 127.0.0.1:8080...
* Established connection to 127.0.0.1 (127.0.0.1 port 8080) from 127.0.0.1 port 50736
* using HTTP/1.x
> GET /F HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.23.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 500 Internal Server Error
< Server: Caddy
< Date: Mon, 09 Nov 2026 19:05:52 GMT
< Content-Length: 0
<
* Connection #0 to host 127.0.0.1:8080 left intact
$ curl -v http://127.0.0.1:8080/Forum/
* Trying 127.0.0.1:8080...
* Established connection to 127.0.0.1 (127.0.0.1 port 8080) from 127.0.0.1 port 50752
* using HTTP/1.x
> GET /Forum/ HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.23.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 500 Internal Server Error
< Server: Caddy
< Date: Mon, 09 Nov 2026 19:05:55 GMT
< Content-Length: 0
<
* Connection #0 to host 127.0.0.1:8080 left intact
$ curl -v http://127.0.0.1:8080/Forum/nonexistent
* Trying 127.0.0.1:8080...
* Established connection to 127.0.0.1 (127.0.0.1 port 8080) from 127.0.0.1 port 40632
* using HTTP/1.x
> GET /Forum/nonexistent HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.23.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 500 Internal Server Error
< Server: Caddy
< Date: Mon, 09 Nov 2026 19:05:59 GMT
< Content-Length: 0
<
* Connection #0 to host 127.0.0.1:8080 left intact
$ curl -v http://127.0.0.1:8080/Forum/DCForumID1
* Trying 127.0.0.1:8080...
* Established connection to 127.0.0.1 (127.0.0.1 port 8080) from 127.0.0.1 port 40646
* using HTTP/1.x
> GET /Forum/DCForumID1 HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.23.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 500 Internal Server Error
< Server: Caddy
< Date: Mon, 09 Nov 2026 19:06:07 GMT
< Content-Length: 0
<
* Connection #0 to host 127.0.0.1:8080 left intact
$ curl -v http://127.0.0.1:8080/Forum/DCForumID1/123.html
* Trying 127.0.0.1:8080...
* Established connection to 127.0.0.1 (127.0.0.1 port 8080) from 127.0.0.1 port 60798
* using HTTP/1.x
> GET /Forum/DCForumID1/123.html HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.23.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 500 Internal Server Error
< Server: Caddy
< Date: Mon, 09 Nov 2026 19:06:12 GMT
< Content-Length: 0
<
* Connection #0 to host 127.0.0.1:8080 left intact
Server logs talk about “no such overload” while “evaluating expression”; my best guess (based on the CEL spec saying that comparisons require both sides to be the same type) is that this means {map_status} is being set to a string, not a number.
server logs for above requests
2026/11/09 19:05:41.447 INFO serving initial configuration
2026/11/09 19:05:52.277 ERROR http.matchers.expression evaluating expression {"error": "no such overload"}
2026/11/09 19:05:52.277 ERROR http.log.error.log0 no such overload {"request": {"remote_ip": "127.0.0.1", "remote_port": "50736", "client_ip": "127.0.0.1", "proto": "HTTP/1.1", "method": "GET", "host": "127.0.0.1:8080", "uri": "/F", "headers": {"User-Agent": ["curl/8.23.0"], "Accept": ["*/*"]}}, "duration": 0.000124804, "error": "no such overload"}
2026/11/09 19:05:52.277 ERROR http.log.access.log0 handled request {"request": {"remote_ip": "127.0.0.1", "remote_port": "50736", "client_ip": "127.0.0.1", "proto": "HTTP/1.1", "method": "GET", "host": "127.0.0.1:8080", "uri": "/F", "headers": {"User-Agent": ["curl/8.23.0"], "Accept": ["*/*"]}}, "bytes_read": 0, "user_id": "", "duration": 0.000124804, "size": 0, "status": 500, "resp_headers": {"Server": ["Caddy"]}}
2026/11/09 19:05:55.386 ERROR http.matchers.expression evaluating expression {"error": "no such overload"}
2026/11/09 19:05:55.386 ERROR http.log.error.log0 no such overload {"request": {"remote_ip": "127.0.0.1", "remote_port": "50752", "client_ip": "127.0.0.1", "proto": "HTTP/1.1", "method": "GET", "host": "127.0.0.1:8080", "uri": "/Forum/", "headers": {"Accept": ["*/*"], "User-Agent": ["curl/8.23.0"]}}, "duration": 0.00009596, "error": "no such overload"}
2026/11/09 19:05:55.386 ERROR http.log.access.log0 handled request {"request": {"remote_ip": "127.0.0.1", "remote_port": "50752", "client_ip": "127.0.0.1", "proto": "HTTP/1.1", "method": "GET", "host": "127.0.0.1:8080", "uri": "/Forum/", "headers": {"Accept": ["*/*"], "User-Agent": ["curl/8.23.0"]}}, "bytes_read": 0, "user_id": "", "duration": 0.00009596, "size": 0, "status": 500, "resp_headers": {"Server": ["Caddy"]}}
2026/11/09 19:05:59.589 ERROR http.matchers.expression evaluating expression {"error": "no such overload"}
2026/11/09 19:05:59.589 ERROR http.log.error.log0 no such overload {"request": {"remote_ip": "127.0.0.1", "remote_port": "40632", "client_ip": "127.0.0.1", "proto": "HTTP/1.1", "method": "GET", "host": "127.0.0.1:8080", "uri": "/Forum/nonexistent", "headers": {"Accept": ["*/*"], "User-Agent": ["curl/8.23.0"]}}, "duration": 0.000098524, "error": "no such overload"}
2026/11/09 19:05:59.589 ERROR http.log.access.log0 handled request {"request": {"remote_ip": "127.0.0.1", "remote_port": "40632", "client_ip": "127.0.0.1", "proto": "HTTP/1.1", "method": "GET", "host": "127.0.0.1:8080", "uri": "/Forum/nonexistent", "headers": {"User-Agent": ["curl/8.23.0"], "Accept": ["*/*"]}}, "bytes_read": 0, "user_id": "", "duration": 0.000098524, "size": 0, "status": 500, "resp_headers": {"Server": ["Caddy"]}}
2026/11/09 19:06:07.419 ERROR http.matchers.expression evaluating expression {"error": "no such overload"}
2026/11/09 19:06:07.419 ERROR http.log.error.log0 no such overload {"request": {"remote_ip": "127.0.0.1", "remote_port": "40646", "client_ip": "127.0.0.1", "proto": "HTTP/1.1", "method": "GET", "host": "127.0.0.1:8080", "uri": "/Forum/DCForumID1", "headers": {"User-Agent": ["curl/8.23.0"], "Accept": ["*/*"]}}, "duration": 0.000098795, "error": "no such overload"}
2026/11/09 19:06:07.420 ERROR http.log.access.log0 handled request {"request": {"remote_ip": "127.0.0.1", "remote_port": "40646", "client_ip": "127.0.0.1", "proto": "HTTP/1.1", "method": "GET", "host": "127.0.0.1:8080", "uri": "/Forum/DCForumID1", "headers": {"User-Agent": ["curl/8.23.0"], "Accept": ["*/*"]}}, "bytes_read": 0, "user_id": "", "duration": 0.000098795, "size": 0, "status": 500, "resp_headers": {"Server": ["Caddy"]}}
2026/11/09 19:06:12.431 ERROR http.matchers.expression evaluating expression {"error": "no such overload"}
2026/11/09 19:06:12.431 ERROR http.log.error.log0 no such overload {"request": {"remote_ip": "127.0.0.1", "remote_port": "60798", "client_ip": "127.0.0.1", "proto": "HTTP/1.1", "method": "GET", "host": "127.0.0.1:8080", "uri": "/Forum/DCForumID1/123.html", "headers": {"User-Agent": ["curl/8.23.0"], "Accept": ["*/*"]}}, "duration": 0.00010656, "error": "no such overload"}
2026/11/09 19:06:12.431 ERROR http.log.access.log0 handled request {"request": {"remote_ip": "127.0.0.1", "remote_port": "60798", "client_ip": "127.0.0.1", "proto": "HTTP/1.1", "method": "GET", "host": "127.0.0.1:8080", "uri": "/Forum/DCForumID1/123.html", "headers": {"User-Agent": ["curl/8.23.0"], "Accept": ["*/*"]}}, "bytes_read": 0, "user_id": "", "duration": 0.00010656, "size": 0, "status": 500, "resp_headers": {"Server": ["Caddy"]}}
3. Caddy version:
$ caddy version
v2.11.7
4. How I installed and ran Caddy:
NixOS package of Caddy (/nix/store/ab61h0mbs9yr6n9558pi0lq0c2fj5i77-caddy-2.11.7/bin/caddy)
a. System environment:
NixOS 26.05
b. Command:
Manual caddy run from a terminal, as an unprivileged user.
c. Service/unit/compose file:
n/a
d. My complete Caddy config:
This is cut down from the full configuration, but I verified it shows the problem described. If you want to try it for yourself, change the root directive to point to an empty directory that actually exists on your system, or caddy may barf on startup. Note all logging is being sent to stdout; the assumption is you’ll do caddy run in a terminal window.
{
http_port 8080
https_port 8088
default_bind 127.0.0.1
admin off
persist_config off
auto_https off
log {
output stdout
level DEBUG
}
debug
}
http://127.0.0.1:8080 {
root /srv/forum-mirror
encode
log {
output stdout
level DEBUG
}
map {path} {map_status} {map_location} {
#...
~^/Forum/(DCForumID[0-9]+)/?$ 308 /Forum-docs/${1}/
~^/Forum/(DCForumID[0-9]+)/(.+)$ 308 /Forum-docs/${1}/${2}
~^/Forum/[^/]+/.+$ 404 -
#...
default 200 -
}
@map_redir `{map_status} in [301, 302, 307, 308]`
@map_error `{map_status} >= 400 && {map_status} < 600`
handle @map_redir {
redir {map_location} {map_status}
}
handle @map_error {
error {map_status}
}
file_server
}
5. Links to relevant resources:
Type here