caddyserver/caddy · error · caddyhttp.Error
invalid request path
Error message
invalid request path
What it means
The FastCGI transport rejects any request whose URL path contains a NUL byte (`\x00`), returning HTTP 400 before dialing the backend. This is a security guard (Caddy issue #4574): PHP backends can be tricked into executing a non-PHP file as PHP code when null bytes survive into SCRIPT_FILENAME/PATH_INFO.
Source
Thrown at modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go:168
// http3 requests have a negative content length for GET and HEAD requests, if that header is not sent.
// see: https://github.com/caddyserver/caddy/issues/6678#issuecomment-2472224182
// Though it appears even if CONTENT_LENGTH is invalid, php-fpm can handle just fine if the body is empty (no Stdin records sent).
// php-fpm will hang if there is any data in the body though, https://github.com/caddyserver/caddy/issues/5420#issuecomment-2415943516
// TODO: better default buffering for fastcgi requests without content length, in theory a value of 1 should be enough, make it bigger anyway
func (t Transport) DefaultBufferSizes() (int64, int64) {
return 4096, 0
}
// RoundTrip implements http.RoundTripper.
func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) {
server := r.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server)
// Disallow null bytes in the request path, because
// PHP upstreams may do bad things, like execute a
// non-PHP file as PHP code. See #4574
if strings.Contains(r.URL.Path, "\x00") {
return nil, caddyhttp.Error(http.StatusBadRequest, fmt.Errorf("invalid request path"))
}
env, err := t.buildEnv(r)
if err != nil {
return nil, fmt.Errorf("building environment: %v", err)
}
ctx := r.Context()
// extract dial information from request (should have been embedded by the reverse proxy)
network, address := "tcp", r.URL.Host
if dialInfo, ok := reverseproxy.GetDialInfo(ctx); ok {
network = dialInfo.Network
address = dialInfo.Address
}
logCreds := server.Logs != nil && server.Logs.ShouldLogCredentials
loggableReq := caddyhttp.LoggableHTTPRequest{View on GitHub (pinned to 50e54ee279)
Solutions
- No server fix is needed for legitimate traffic: reject/ignore such requests, they are malicious or broken
- Ensure the fastcgi transport is only matched by routes you intend (e.g. `php_server` matcher), so static files are served by file_server instead
- Keep Caddy updated so the null-byte guard is present in your deployment
- Harden the PHP backend (php-cgi) independently, since this guard is defense-in-depth
Example fix
# before: fastcgi transport applied to everything
example.com {
root * /srv
reverse_proxy localhost:9000 {
transport fastcgi
}
}
# after: only route PHP index requests through fastcgi
example.com {
root * /srv
php_server
file_server
} Defensive patterns
Strategy: validation
Validate before calling
// server-side: nothing to guard; this is an intentional 400 for malicious paths.
// In tests, assert the behavior:
req := httptest.NewRequest("GET", "/file.png%00.php", nil)
// expect 400 from the fastcgi transport route Prevention
- Do not attempt to bypass the check; it protects PHP backends from code execution
- Scope the fastcgi transport narrowly (php_server matcher) so static files never reach it
- Log 400s on %00 paths as security signal, not as errors to fix
When it happens
Trigger: A client request with `%00` URL-encoded into the path (e.g. `/uploads/avatar.png%00.php`) reaching a reverse_proxy route using the fastcgi transport. The check is `strings.Contains(r.URL.Path, "\x00")` on the decoded path.
Common situations: Automated scanners probing php-cgi setups; crafted uploads where an attacker appends %00.php to a static file; occasionally broken clients that embed NULs in paths.
Related errors
- illegal ADS path
- reading request body: %v
- loading config: %v
- invalid Content-Type: %v
- unrecognized config adapter '%s'
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/2445fd118120ff91.
Report an issue: GitHub.