Caddy 2.11.7 → 2.11.9
2 releases · 18 breaking changes · 15 security fixes · 5 advisories fixed
15security18breaking0deprecated24feature0fix
Vulnerabilities fixed by this upgrade
- 5.9 GHSA-f278-mgc7-46j3 php_fastcgi authorization bypass via dot-segments in the request path
- medium GHSA-jf74-f69g-hcj9 file_server exposes private keys when the data directory falls back to ./caddy
- medium GHSA-69hv-5jfc-mvmv rewrite re-decodes {http.matchers.file.remainder}, bypassing path-based deny rules
- medium GHSA-25cw-38mq-q7m6 file matcher resolves /file/./ to a regular file, bypassing suffix path rules
- high GHSA-r766-rf9f-588h Host and :authority values with dot segments or slashes escape a {host}-based root
Breaking changes
- breaking Conflicting client_auth is now a Caddyfile error. When two site blocks share TLS connection policies for the same hostname but set different client_auth, the adapter used to keep the first one and silently drop the other, which could quietly remove a stricter mTLS requirement. (#8306) in 2.11.9
- breaking FastCGI requests that are ambiguous about which script they run get 400: in 2.11.9
- breaking when the escaped path (which %-containing path matchers see) and the decoded path would select different scripts, e.g. /public/tool.php%2f..%2f..%2fadmin%2ftool.php (#8357) in 2.11.9
- breaking on Windows, script paths with an 8.3 short name component (like ADMINI~1) or a : (NTFS alternate data streams) (#8356) in 2.11.9
- breaking FastCGI env values now take precedence over request headers, so a client can no longer override HTTP_* variables you set with env (see below). (#8358) in 2.11.9
- breaking Requests with an invalid or dots-only host (like ..) are rejected with 400 on every protocol, before any route runs. Before, HTTP/2 and HTTP/3 also accepted hosts containing / or . (#8328) in 2.11.8
- breaking request_body max_size now rejects a request with 413 as soon as its Content-Length is over the limit, even if nothing would have read the body. (#8337) in 2.11.8
- breaking php_fastcgi sites without an index.php: requests for paths that don't exist are now sent to PHP as index.php, as the documented first_exist_fallback try policy always promised, instead of falling through to file_server and returning 404. Also, PATH_INFO and DOCUMENT_URI are now derived from the… in 2.11.8
- breaking A try_files / file candidate ending in / only matches a directory, so /foo.txt/ no longer matches the file /foo.txt. (#8326) in 2.11.8
- breaking encode now compresses responses even when the request has Cache-Control: no-transform (a response's no-transform is still honored), and responses that could have been encoded always get Vary: Accept-Encoding. To keep the old behavior: @transformable not header Cache-Control no-transform and encode… in 2.11.8
- breaking Stricter config validation. Some configs that were silently accepted (and didn't do what you'd expect) are now errors: in 2.11.8
- breaking more than one expression line in the same matcher set (combine them with &&) (#8183) in 2.11.8
- breaking unknown TLS curves or protocol_min / protocol_max names, like TLS1.3 instead of tls1.3, which used to be silently ignored (#8266) in 2.11.8
- breaking a repeated sni_regexp in a TLS policy match block (#8137) in 2.11.8
- breaking ip_mask log filter sizes out of range, like 33 for IPv4 (#8241) in 2.11.8
- breaking basic_auth usernames that end up identical after placeholder expansion (#8315) in 2.11.8
- breaking ports longer than 11 characters (#8277) in 2.11.8
- breaking Evaluation order now follows the Caddyfile for handle_errors blocks with status codes and for vars with non-path matchers. If you had overlapping codes or set the same variable twice, a different block or value may now win. (#8195, #8197) in 2.11.8
Security changes
- security fastcgi: A client could override any HTTP_* variable set with the FastCGI transport's (or php_fastcgi's) env subdirective by sending a matching request header, for example choosing the user an application sees in HTTP_X_AUTH_USER. Configured values now win. Reported by @JebeenLee and @Camel4,… in 2.11.9
- security fastcgi: On Windows, NTFS 8.3 short names and alternate data streams could run a script that a path rule was meant to deny, completing the FastCGI fix from 2.11.8. Thanks @dunglas. (GHSA-9cr8-wmhh-39pm, #8356) in 2.11.9
- security fastcgi: Encoded slashes and dot segments could make a %-containing path matcher approve one script while another one runs. Thanks @dunglas. (#8357) in 2.11.9
- security fastcgi: Over HTTP/2, a host field sent next to :authority could make the application see a different host (HTTP_HOST) than the one Caddy routed the request to. Thanks @dunglas. (#8355) in 2.11.9
- security httpcaddyfile: A stricter client_auth could be silently dropped when merging TLS connection policies (see above). Thanks @sam-kota. (#8306) in 2.11.9
- security Updated go-jose to v4.1.6 for GHSA-mgcc-cww5-fp2q. (#8299) in 2.11.9
- security caddyhttp: A Host / :authority value with dot segments or slashes could escape the intended directory when used in file paths, like root /srv/sites/{host}, and with php_fastcgi even execute files outside it. Such hosts are now rejected (see above). Thanks @dunglas. (GHSA-r766-rf9f-588h, #8328) in 2.11.8
- security fastcgi: SCRIPT_FILENAME is now derived from the same normalized path that path matchers see, so a route protected by a path matcher can't be made to run a different script, e.g. with /admin.php/../../other.php. The php_fastcgi rewrite also always runs now (see above). Thanks @pamnario.… in 2.11.8
- security fileserver: The file matcher no longer resolves paths like /.env/./ to the regular file, which bypassed path rules such as path *.env. Thanks @dunglas. (GHSA-25cw-38mq-q7m6, #8326) in 2.11.8
- security rewrite: {http.matchers.file.remainder} is now escaped before rewriting, so double-encoded path info (like ..%252f) can't turn into ../ after path rules have run. Thanks @dunglas. (GHSA-69hv-5jfc-mvmv, #8325) in 2.11.8
- security fileserver: When neither HOME nor XDG_DATA_HOME is set, Caddy's data directory falls back to ./caddy, and a file_server serving the working directory could expose certificates and private keys. That directory is now always hidden. Thanks @dunglas. (GHSA-jf74-f69g-hcj9, #8327) in 2.11.8
- security fastcgi: The php_fastcgi trailing-slash redirect for directories with an index.php can no longer be turned into an open redirect to another site. Thanks @sam-kota. (#8285) in 2.11.8
- security fileserver: Files excluded with hide no longer show up in browse listings, and hidden precompressed sidecars are no longer served. Thanks @agammann. (#8155) in 2.11.8
- security reverseproxy: Hop-by-hop headers are stripped from 1xx responses and from copy_response_headers in handle_response, completing the fix for CVE-2026-55458. Thanks @SssiiiSssii1 and @tomholford. (GHSA-8qx7-j2v2-x6q5, #8256, #8270) in 2.11.8
- security logging: The query log filter now parses request URIs like the server does, so parameters after a # are redacted too. Thanks @jayhemnani9910. (#8255) in 2.11.8
Every release in between
featureFixed: PHP directory indexes (regression in 2.11.8). A request for a directory with a trailing slash, like /wp-admin/, was matched by the directory itself instead of its index.php, so it never reached PHP and usually got a 404. This broke WordPress admin pages and other apps that serve an index…
featurePath matchers with placeholders now work as expected: placeholders like {env.FOO} are no longer lower-cased into {env.foo} before they're looked up, and their values are compared case-insensitively against the request path, like static patterns. Thanks @BenMacha! (#8343)
featureNew plain_time_format option for file_server browse: sets the time layout of plain-text directory listings (Accept: text/plain). Thanks @steffenbusch! (#8084)
featureFewer allocations in path cleaning, header rewriting and weighted_round_robin selection. Thanks @jvoisin! (#7842)
featureFor plugin authors: new httpcaddyfile.Helper.IsNamedRoute() tells a directive whether it's being parsed inside a named route. (#8354)
breakingConflicting client_auth is now a Caddyfile error. When two site blocks share TLS connection policies for the same hostname but set different client_auth, the adapter used to keep the first one and silently drop the other, which could quietly remove a stricter mTLS requirement. (#8306)
breakingFastCGI requests that are ambiguous about which script they run get 400:
breakingwhen the escaped path (which %-containing path matchers see) and the decoded path would select different scripts, e.g. /public/tool.php%2f..%2f..%2fadmin%2ftool.php (#8357)
breakingon Windows, script paths with an 8.3 short name component (like ADMINI~1) or a : (NTFS alternate data streams) (#8356)
breakingFastCGI env values now take precedence over request headers, so a client can no longer override HTTP_* variables you set with env (see below). (#8358)
securityfastcgi: A client could override any HTTP_* variable set with the FastCGI transport's (or php_fastcgi's) env subdirective by sending a matching request header, for example choosing the user an application sees in HTTP_X_AUTH_USER. Configured values now win. Reported by @JebeenLee and @Camel4,…
securityfastcgi: On Windows, NTFS 8.3 short names and alternate data streams could run a script that a path rule was meant to deny, completing the FastCGI fix from 2.11.8. Thanks @dunglas. (GHSA-9cr8-wmhh-39pm, #8356)
securityfastcgi: Encoded slashes and dot segments could make a %-containing path matcher approve one script while another one runs. Thanks @dunglas. (#8357)
securityfastcgi: Over HTTP/2, a host field sent next to :authority could make the application see a different host (HTTP_HOST) than the one Caddy routed the request to. Thanks @dunglas. (#8355)
securityhttpcaddyfile: A stricter client_auth could be silently dropped when merging TLS connection policies (see above). Thanks @sam-kota. (#8306)
securityUpdated go-jose to v4.1.6 for GHSA-mgcc-cww5-fp2q. (#8299)
othercaddyhttp: lower-case path patterns with placeholders at request time
otherhttpcaddyfile: reject conflicting client_auth when merging TLS policies
otherbuild(deps): bump the actions-deps group across 1 directory with 10 updates
otherbuild(deps): bump github.com/go-jose/go-jose/v4 from 4.1.5 to 4.1.6
otherfileserver: make plain-text browse time format configurable
otherfileserver: only match a dir when the pattern ends by /
featurehttpcaddyfile: add Helper.IsNamedRoute
otherfastcgi: take HTTP_HOST from the routed host only
otherfastcgi: reject Win 8.3 aliases and alt data streams paths
othercaddyhttp: presize buffers in hot paths to reduce allocations
otherfastcgi: let configured env variables override request headers
otherfastcgi: reject paths whose escaped form selects another script
featureIdle timeouts, round two. More fixes for the idle read/write timeouts introduced in 2.11.6:
featurePer-route timeouts now take precedence over the server-wide read_body_idle / write_idle, and nested timeouts use the innermost one. (#8121)
featureOver HTTP/2, a handler that paused reading the request body (like reverse_proxy uploading to a slow upstream) no longer loses the body and fails with 502. (#8125)
featureDeadlines set by handlers now act as a ceiling instead of being overridden, so things like Mercure's dispatch_timeout and FrankenPHP's request_body_timeout work again. Thanks @dunglas! (#8145, #8284)
featureHTTP/1.1 uploads that time out with a little body left no longer hang instead of sending the error response. Thanks @SiyXo511! (#8178)
featureBinding privileged ports with fewer capabilities (Linux): If CAP_NET_BIND_SERVICE is permitted but not effective, Caddy raises it just for binding a privileged port. Packagers can use setcap cap_net_bind_service=+p instead of +ep, so Caddy still starts under --cap-drop ALL, OpenShift's…
featurenew log_level directive to set the access log level for matching requests, e.g. log_level @healthchecks DEBUG (#8201)
featurenew {time.now.rfc3339} placeholder. Thanks @steadytao! (#8140)
featurenew not matcher for TLS connection policies. Thanks @simonether! (#8138)
featurethe tls matcher can be used in CEL expression matchers: tls(), tls('complete'), tls('early_data'). Thanks @stackedbyaradhya! (#8088)
featureheaders you set yourself (like Strict-Transport-Security via header) are no longer lost when the upstream sends a 1xx response first, like 103 Early Hints (#8169)
featurerequest trailer values are forwarded, not just their names. Thanks @krishsinghhura! (#8246)
featurerequest-only expressions in lb_retry_match no longer retry successful responses (a regression since 2.11.3) (#8168)
featuredial errors through a forward proxy and to FastCGI backends are retried like other dial errors. Thanks @adarshxsh and @zemamba! (#8264, #8172)
featureweighted_round_robin no longer panics with fewer weights than upstreams, and no longer picks an unavailable upstream when there's only one weight; random_choose picks the least loaded host when all are busy (#8159, #8320, #8160)
featurewith request_buffers / response_buffers unlimited, a body that fails partway is no longer treated as complete (#8207)
featureconnections and sockets are no longer leaked when a protocol upgrade (e.g. WebSocket) fails during setup, or when a handle_response status isn't a number. Thanks @Version2beta and @BenMacha! (#8269, #8318)
featureHTTP/3 client cancellations are logged at debug level instead of as warnings. Thanks @larrasket! (#8135)
breakingRequests with an invalid or dots-only host (like ..) are rejected with 400 on every protocol, before any route runs. Before, HTTP/2 and HTTP/3 also accepted hosts containing / or . (#8328)
breakingrequest_body max_size now rejects a request with 413 as soon as its Content-Length is over the limit, even if nothing would have read the body. (#8337)
breakingphp_fastcgi sites without an index.php: requests for paths that don't exist are now sent to PHP as index.php, as the documented first_exist_fallback try policy always promised, instead of falling through to file_server and returning 404. Also, PATH_INFO and DOCUMENT_URI are now derived from the…
breakingA try_files / file candidate ending in / only matches a directory, so /foo.txt/ no longer matches the file /foo.txt. (#8326)
breakingencode now compresses responses even when the request has Cache-Control: no-transform (a response's no-transform is still honored), and responses that could have been encoded always get Vary: Accept-Encoding. To keep the old behavior: @transformable not header Cache-Control no-transform and encode…
breakingStricter config validation. Some configs that were silently accepted (and didn't do what you'd expect) are now errors:
breakingmore than one expression line in the same matcher set (combine them with &&) (#8183)
breakingunknown TLS curves or protocol_min / protocol_max names, like TLS1.3 instead of tls1.3, which used to be silently ignored (#8266)
breakinga repeated sni_regexp in a TLS policy match block (#8137)
breakingip_mask log filter sizes out of range, like 33 for IPv4 (#8241)
breakingbasic_auth usernames that end up identical after placeholder expansion (#8315)
breakingports longer than 11 characters (#8277)
breakingEvaluation order now follows the Caddyfile for handle_errors blocks with status codes and for vars with non-path matchers. If you had overlapping codes or set the same variable twice, a different block or value may now win. (#8195, #8197)
securitycaddyhttp: A Host / :authority value with dot segments or slashes could escape the intended directory when used in file paths, like root /srv/sites/{host}, and with php_fastcgi even execute files outside it. Such hosts are now rejected (see above). Thanks @dunglas. (GHSA-r766-rf9f-588h, #8328)
securityfastcgi: SCRIPT_FILENAME is now derived from the same normalized path that path matchers see, so a route protected by a path matcher can't be made to run a different script, e.g. with /admin.php/../../other.php. The php_fastcgi rewrite also always runs now (see above). Thanks @pamnario.…
securityfileserver: The file matcher no longer resolves paths like /.env/./ to the regular file, which bypassed path rules such as path *.env. Thanks @dunglas. (GHSA-25cw-38mq-q7m6, #8326)
securityrewrite: {http.matchers.file.remainder} is now escaped before rewriting, so double-encoded path info (like ..%252f) can't turn into ../ after path rules have run. Thanks @dunglas. (GHSA-69hv-5jfc-mvmv, #8325)
securityfileserver: When neither HOME nor XDG_DATA_HOME is set, Caddy's data directory falls back to ./caddy, and a file_server serving the working directory could expose certificates and private keys. That directory is now always hidden. Thanks @dunglas. (GHSA-jf74-f69g-hcj9, #8327)
securityfastcgi: The php_fastcgi trailing-slash redirect for directories with an index.php can no longer be turned into an open redirect to another site. Thanks @sam-kota. (#8285)
securityfileserver: Files excluded with hide no longer show up in browse listings, and hidden precompressed sidecars are no longer served. Thanks @agammann. (#8155)
securityreverseproxy: Hop-by-hop headers are stripped from 1xx responses and from copy_response_headers in handle_response, completing the fix for CVE-2026-55458. Thanks @SssiiiSssii1 and @tomholford. (GHSA-8qx7-j2v2-x6q5, #8256, #8270)
securitylogging: The query log filter now parses request URIs like the server does, so parameters after a # are redacted too. Thanks @jayhemnani9910. (#8255)
+109 more items in the release notes