Requesting filenames containing "?" via file_server while using php_fastcgi results in a truncation

1. The problem I’m having:

Normally, when I request /file%3F.txt/file%3F.txt, it should actually request /file?.txt/file?.txt, and using file_server directly works fine. However, if I have a php_fastcgi block, what gets passed to file_server seems to be /file, as if everything after the question mark was stripped away.

2. Error messages and/or full log output:

cat /root/caddytest/file
fake file
cat '/root/caddytest/file?.txt/file?.txt' 
real file
curl http://localhost/file%3F.txt/file%3F.txt
fake file
caddy run --config Caddyfile 
2026/01/14 07:24:41.620	INFO	maxprocs: Leaving GOMAXPROCS=1: CPU quota undefined
2026/01/14 07:24:41.621	INFO	GOMEMLIMIT is updated	{"GOMEMLIMIT": 913464115, "previous": 9223372036854775807}
2026/01/14 07:24:41.621	INFO	using config from file	{"file": "Caddyfile"}
2026/01/14 07:24:41.621	INFO	adapted config to JSON	{"adapter": "caddyfile"}
2026/01/14 07:24:41.621	INFO	admin	admin endpoint started	{"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2026/01/14 07:24:41.622	WARN	http.auto_https	server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server	{"server_name": "srv0", "http_port": 80}
2026/01/14 07:24:41.622	DEBUG	http.auto_https	all servers have automatic HTTPS disabled and no domains need certificates, skipping TLS automation setup
2026/01/14 07:24:41.623	DEBUG	http	starting server loop	{"address": "[::]:80", "tls": false, "http3": false}
2026/01/14 07:24:41.623	WARN	http	HTTP/2 skipped because it requires TLS	{"network": "tcp", "addr": ":80"}
2026/01/14 07:24:41.623	WARN	http	HTTP/3 skipped because it requires TLS	{"network": "tcp", "addr": ":80"}
2026/01/14 07:24:41.623	INFO	http.log	server running	{"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2026/01/14 07:24:41.623	DEBUG	events	event	{"name": "started", "id": "308d8cc8-dc68-45c8-9b82-564deff668f4", "origin": "", "data": null}
2026/01/14 07:24:41.623	INFO	autosaved config (load with --resume flag)	{"file": "/root/.config/caddy/autosave.json"}
2026/01/14 07:24:41.623	INFO	serving initial configuration
2026/01/14 07:24:41.634	INFO	tls	storage cleaning happened too recently; skipping for now	{"storage": "FileStorage:/root/.local/share/caddy", "instance": "d61649d5-2acd-47b9-9e1f-ae148ea2df69", "try_again": "2026/01/15 07:24:41.634", "try_again_in": 86399.999999198}
2026/01/14 07:24:41.634	INFO	tls	finished cleaning storage units
2026/01/14 07:24:41.635	INFO	tls.cache.maintenance	started background certificate maintenance	{"cache": "0xc000406200"}
2026/01/14 07:24:43.049	DEBUG	http.handlers.rewrite	rewrote request	{"request": {"remote_ip": "::1", "remote_port": "36480", "client_ip": "::1", "proto": "HTTP/1.1", "method": "GET", "host": "localhost", "uri": "/file", "headers": {"User-Agent": ["curl/8.14.1"], "Accept": ["*/*"]}}, "method": "GET", "uri": "/file"}
2026/01/14 07:24:43.050	DEBUG	http.handlers.file_server	sanitized path join	{"site_root": "/root/caddytest", "fs": "", "request_path": "/file", "result": "/root/caddytest/file"}
2026/01/14 07:24:43.050	DEBUG	http.handlers.file_server	opening file	{"filename": "/root/caddytest/file"}

3. Caddy version:

e40bd019ff4599b34769d249ee37934217ed9fa4 (14 Jan 26 00:06 UTC)

4. How I installed and ran Caddy:

a. System environment:

I actually use systemd, but I can reproduce the issue by simply running caddy run.

b. Command:

caddy run --config Caddyfile

c. Service/unit/compose file:

none

d. My complete Caddy config:

{
	debug
}
:80 {
	root * /root/caddytest
	php_fastcgi unix//run/php/php8.4-fpm.sock
	file_server
}

5. Links to relevant resources:

none

Using file_server directly:

Caddyfile

{
	debug
}
:80 {
	root * /root/caddytest
	file_server
}

log

curl http://localhost/file%3F.txt/file%3F.txt
real file
caddy run --config Caddyfile 
2026/01/14 07:34:41.653	INFO	maxprocs: Leaving GOMAXPROCS=1: CPU quota undefined
2026/01/14 07:34:41.653	INFO	GOMEMLIMIT is updated	{"GOMEMLIMIT": 913464115, "previous": 9223372036854775807}
2026/01/14 07:34:41.653	INFO	using config from file	{"file": "Caddyfile"}
2026/01/14 07:34:41.653	INFO	adapted config to JSON	{"adapter": "caddyfile"}
2026/01/14 07:34:41.654	INFO	admin	admin endpoint started	{"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2026/01/14 07:34:41.654	WARN	http.auto_https	server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server	{"server_name": "srv0", "http_port": 80}
2026/01/14 07:34:41.654	DEBUG	http.auto_https	all servers have automatic HTTPS disabled and no domains need certificates, skipping TLS automation setup
2026/01/14 07:34:41.654	DEBUG	http	starting server loop	{"address": "[::]:80", "tls": false, "http3": false}
2026/01/14 07:34:41.654	WARN	http	HTTP/2 skipped because it requires TLS	{"network": "tcp", "addr": ":80"}
2026/01/14 07:34:41.654	WARN	http	HTTP/3 skipped because it requires TLS	{"network": "tcp", "addr": ":80"}
2026/01/14 07:34:41.654	INFO	http.log	server running	{"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2026/01/14 07:34:41.654	DEBUG	events	event	{"name": "started", "id": "4b02d944-0104-469d-8ed2-c32821ef130a", "origin": "", "data": null}
2026/01/14 07:34:41.655	INFO	autosaved config (load with --resume flag)	{"file": "/root/.config/caddy/autosave.json"}
2026/01/14 07:34:41.655	INFO	serving initial configuration
2026/01/14 07:34:41.656	INFO	tls	storage cleaning happened too recently; skipping for now	{"storage": "FileStorage:/root/.local/share/caddy", "instance": "d61649d5-2acd-47b9-9e1f-ae148ea2df69", "try_again": "2026/01/15 07:34:41.656", "try_again_in": 86399.999999639}
2026/01/14 07:34:41.656	INFO	tls	finished cleaning storage units
2026/01/14 07:34:41.656	INFO	tls.cache.maintenance	started background certificate maintenance	{"cache": "0xc000260900"}
2026/01/14 07:34:44.164	DEBUG	http.handlers.file_server	sanitized path join	{"site_root": "/root/caddytest", "fs": "", "request_path": "/file?.txt/file?.txt", "result": "/root/caddytest/file?.txt/file?.txt"}
2026/01/14 07:34:44.165	DEBUG	http.handlers.file_server	opening file	{"filename": "/root/caddytest/file?.txt/file?.txt"}