This patch release contains a large number of minor and some noticeable enhancements and bug fixes. Thank you to everyone who contributed or spent their LLM tokens responsibly to help with this release!
We have much more in the pipeline still, as AI has made contributions of all quality levels cheap and easy. We will be trying to go through them as quickly and efficiently as we can.
Huge thank you to our sponsors for keeping the project alive with resources, and for our maintainers who triage and assist tirelessly in this relentless new age of AI.
⚠️ Please read the breaking changes below before upgrading. Most of them come from security hardening, and most configs won't notice. If you were relying on one of the old behaviors, though, you'll want to know about it.
Highlights
- New
url_patternrequest matcher: Match requests with the URLPattern standard, the same syntax used by browsers (JS) and many web frameworks. It supports named groups, wildcards, and regexp components. Captured groups become placeholders ({http.url_pattern.<component>.<group>}), and there's a matchingurl_patternCEL function too. Thanks @dunglas! (#7787) - Slowloris mitigation: New idle read/write timeouts reset on every successful read or write. A stalled connection gets cut off, and a slow one that's still making progress is left alone. You can also set optional minimum transfer rates, and there's a new
timeoutshandler directive for per-route tuning. (#7913) - New
tls_automate_namesglobal option: Manage certificates for names without serving them in a site block. (#8015) - New
expected_underscore_headersserver option: If dropping header fields with underscores in 2.11.4 broke your app, you can now list the specific headers to keep. (#7809) - HTTP/3 over Tailscale and other low-MTU links now works, because the initial QUIC packet size is smaller. (#7886)
- Reverse proxy got more love:
- partial responses are flushed to clients properly (#7849)
- TCP half-close is propagated on upgraded streams (#8027)
versions 3upstreams now honortls_trust_pool(#8042)- active health check state is kept separate per check config, so one handler's failing probes don't mark the upstream down for everyone else (#7916)
- the
random_choosepolicy distributes correctly now (#7873)
- Server-sent events behind
encodenow stream immediately instead of being buffered. (#7905) - Graceful shutdown now waits for servers left over from previous configs, so long-lived responses that started before a reload aren't cut off at exit. (#8009)
- Caddyfile:
importnow works inside named routes (#7986), and quoted braces are treated as literal arguments (#7875). - Logging:
- new
set_cookielog filter (#7888) roll_intervalaccepts days (d) (#7900)- recovered handler panics are logged at ERROR level (#7924)
- write timeout errors show up in access logs (#7945)
- new
- Performance: fewer allocations in request hot paths, encoding negotiation, and the reverse proxy, from @jvoisin and @dunglas. Directory browsing is much faster for large directories. (#7847, #7903, #7911, #7925, #7926, #7936)
- Other new stuff:
⚠️ Breaking changes
- Go 1.26 is now the minimum version for building Caddy and plugins. (#8056)
- Request headers are limited to 16 KiB by default. Before, we used Go's 1 MB default. Requests with larger headers (giant cookies, oversized tokens, etc.) will now get
431 Request Header Fields Too Large. If you need more, raise it with themax_header_sizeserver option. - New 1-minute idle read/write timeouts by default. If a request body read or a response write stalls (makes no progress at all) for longer than that, the connection is aborted. Pauses between writes, like with SSE, don't count. Some long-lived streams where the client goes quiet mid-body may be affected. You can tune these with
read_body_idleandwrite_idlein thetimeoutsserver option, or per-route with thetimeoutsdirective. (#7913) - Request header fields containing
.are now dropped, the same way underscores were in 2.11.4. PHP folds.to_, so these could be used to impersonate legitimate headers. If you need specific ones, allow them with the newexpected_dot_headersserver option. - A wildcard site's
client_authno longer applies to more specific hostnames that have their own site blocks. For example,public.example.comno longer inherits mTLS from*.example.com. If you were counting on that inheritance, configureclient_authon the specific site explicitly. (#7920) - Stricter config validation. Some configs that used to be silently accepted (and probably didn't do what you expected) are now errors:
- Admin API
/loadnow returns400with warnings inside a valid JSON body when a config is invalid. Before, it returned200with two concatenated JSON objects. (#7267) methodmatcher values are normalized to uppercase, somethod getnow matchesGETrequests. (#7832)
Security fixes
Thank you to everyone who reported responsibly and helped with patches:
- reverseproxy: When a route used both
forward_authandreverse_proxy, a request could be sent on the wrong upstream connection. Reported by @carlt, fixed by @WeidiDeng. (GHSA-6365-7ppr-5r92, #7859) - reverseproxy: Hop-by-hop headers from upstreams are now stripped from
101 Switching Protocolsresponses too. Thanks @jirn073-76. - caddyhttp:
handle_pathanduristrip_prefix/strip_suffixnow canonicalize the resulting path, so it can't bypass path-based authorization. Thanks @steadytao. - caddyhttp: Extended the 2.11.4 header-alias filter to
.(see above) to prevent bypassingforward_auth copy_headerswith PHP/FastCGI backends. Thanks @dunglas. - caddyhttp: The
path_regexpmatcher now normalizes Windows backslashes like thepathmatcher does. This completes the fix for CVE-2026-52844. Thanks @thientd. (#7858) - fileserver: Windows 8.3 short names are rejected in every path component, not just the last one. Thanks @DavidCarliez. (#7952)
- fileserver: Fixed ETag collisions between files with different modification times and sizes. Thanks @dunglas.
- fastcgi: The client's
Proxyheader is no longer passed to backends asHTTP_PROXY(HTTPoxy). Thanks @bzyy1024. (#7934) - reverseproxy: Sticky session cookie hashes are compared in constant time. Thanks @alhudz. (#7853)
- admin: Request paths are normalized in the remote admin access check, and origin/host allow-lists compare case-insensitively (defense-in-depth). Thanks @AmariahAK, @mohammed90, and @hktitof. (#7910, #7973, #7993)
- caddyhttp: Oversized request bodies used through placeholders now correctly return
413. Thanks @hktitof. (#7969)
⚠️ These security patches may be breaking if your application relies on the buggy behaviors.
🚨 Notice for Caddy plugin maintainers: Dependabot will probably alert you to the security fixes in Caddy and urge you to upgrade it in your go.mod file. Please ONLY upgrade the Caddy dependency if there's a change to an exported API your plugin uses. Note that doing so now also requires Go 1.26.
Thank you to everyone who was involved this release, especially our 40 new contributors! 🎉
What's Changed
- reverseproxy: replace placeholders specified for sni while using http3 by @WeidiDeng in #7737
- caddyhttp: add {http.request.proto_name} placeholder for spec-compliant protocol names by @Jualhosting in #7782
- httpcaddyfile: error on duplicate named_routes by @vijayvenkatj in #7800
- cmd: colored error message in WrapCommandFuncForCobra (#7760) by @u5surf in #7768
- chore: add missing "is" in SECURITY.md by @AliMickey in #7802
- reverseproxy: validate on weighted_round_robin loadbalancing policy by @vijayvenkatj in #7807
- forwardauth: error on duplicate uri subdirective by @vijayvenkatj in #7814
- encode: add standard benchmark and conformance harness by @ottenhoff in #7804
- caddyhttp: restore allow_underscore_in_headers server option by @bluegate-studio in #7809
- rewrite: fix wrong index check in trimPathPrefix by @alhudz in #7812
- intercept: fix replace_status being silently dropped by @oksusucha in #7810
- fileserver: append repeated hide subdirectives instead of overwriting by @luccinmasirika in #7817
- rewrite: scope keyed query replace to its named key by @alhudz in #7818
- reverseproxy: log status 499 instead of 0 when client disconnects by @larrasket in #7827
- tracing: fix BatchSpanProcessor goroutine leak on config reload by @Dean2026 in #7826
- caddyhttp: normalize method names to uppercase in MatchMethod.Provision by @yintaisha in #7832
- caddyhttp: add URL pattern request matcher by @dunglas in #7787
- caddyhttp: fix escaped path matcher over-matching longer paths by @alhudz in #7828
- core: preserve metrics registry in Context.WithValue by @dunglas in #7861
- reverseproxy: compare sticky-session cookie hash in constant time by @alhudz in #7853
- reverseproxy: fix misleading handle_response error for extra matcher args by @TowyTowy in #7869
- reverseproxy: save dial info in a context key instead of a variable k… by @WeidiDeng in #7859
- caddyhttp: fix path_regexp (MatchPathRE) Windows backslash bypass by @thientd in #7858
- intercept: fix misleading handle_response error for extra matcher args by @TowyTowy in #7871
- build(deps): bump cel-go from v0.28.1 to v0.29.2 by @techknowlogick in #7872
- reverseproxy: fix broken reservoir sampling in random_choose policy by @TowyTowy in #7873
- caddyfile: treat quoted braces as literal arguments by @elee1766 in #7875
- logging: hash query parameter values in QueryFilter by @PichuChen in #7884
- chore: fix sabotage spelling in nolint comments by @futurehua in #7892
- pki: Handle error immediately after reading root from disk by @hslatman in #7896
- log: support
d(day) inroll_intervaldirective by @mohammed90 in #7900 - deps: update GitHub Actions and Go modules by @steadytao in #7876
- build(deps): bump google.golang.org/grpc from 1.81.1 to 1.82.1 by @dependabot[bot] in #7908
- core: reduce QUIC initial packet size for low-MTU paths by @Salynn in #7886
- rewrite: preserve non-canonical path encoding after uri replace by @larrasket in #7907
- caddyhttp: use canonical header key casing to avoid re-canonicalization by @dunglas in #7911
- caddyconfig: Register nested named-route invokes transitively by @SillyZir in #7898
- encode: reduce allocations in AcceptedEncodings by @jvoisin in #7847
- encode: flush headers immediately for server-sent events responses by @SillyZir in #7905
- fileserver: speed up directory browsing for large directories by @firefart in #7903
- caddyauth: isolate provider responses to prevent cross-provider clobbering by @SillyZir in #7904
- log: don't allow overwriting singly-assigned vals by @mohammed90 in #7927
- caddyconfig: cancel HTTP loader requests with context by @cuishuang in #7918
- feat(fastcgi): populate SERVER_ADDR by default by @renich in #7912
- reverseproxy: reduce allocation/CPU overhead in request hot paths by @jvoisin in #7925
- reverseproxy: preallocate upstream slices with known sizes by @jvoisin in #7926
- network_proxy: reject proxy URLs that resolve to a port with no host by @r0h1tb in #7922
- caddyhttp: log recovered handler panics at ERROR level by @ousamabenyounes in #7924
- caddyhttp: allocate the request UUID lazily instead of on every request by @jvoisin in #7936
- caddyhttp: shield specific hostnames from a covering wildcard's client auth by @SillyZir in #7920
- reverseproxy: isolate active health-check state per distinct check config by @SillyZir in #7916
- logging: add set_cookie log filter for Set-Cookie response headers by @steffenbusch in #7888
- caddyhttp: fix url_pattern authorization bypass via encoded-slash traversal by @dunglas in #7941
- fix: close resources on error paths by @ittakestwo123 in #7940
- fastcgi: fix HTTPoxy vulnerability by @bzyy1024 in #7934
- caddyhttp: mitigate slowloris via idle read/write deadlines by @dunglas in #7913
- chore: fix lint errors from newer golangci-lint by @faiyazrahmann in #7958
- pki: honor skip_install_trust for explicit tls internal issuers by @gautamrizwani in #7894
- caddyfile: clarify ArgErr documentation by @0jaspahwa in #7960
- rewrite: fix URI splitting when a query or fragment arrives via a placeholder by @francislavoie in #7947
- admin: normalize request path in remote admin access-control check (defense-in-depth) by @AmariahAK in #7910
- fileserver: reject short names in every path component by @DavidCarliez in #7952
- rewrite: fix strip_path_suffix ignoring percent-encoding by @TowyTowy in #7877
- admin: stabilise log redaction test by @steadytao in #7942
- chore: fix canonicalheader failures by @steadytao in #7964
- cmd: upgrade automemlimit to v1.0.0 by @dunglas in #7978
- caddy: fix ParseNetworkAddress port-range span off-by-one by @mohammed90 in #7975
- usagepool: avoid lock inversion after constructor failure by @kojah in #7968
- cmd: reject ambiguous module version separators by @mohammed90 in #7974
- admin: fix origin allow-list host comparison to be case-insensitive by @mohammed90 in #7973
- caddyfile: fix importGraph self-loop and stale-edge bugs by @mohammed90 in #7971
- caddyhttp: remove unused QUICConfig from the HTTP/3 server by @faiyazrahmann in #7980
- fileserver: reject non-integer browse file_limit by @SashaMIT in #7988
- caddyhttp: surface write timeout errors in access log by @faiyazrahmann in #7945
- admin: fix host allow-list comparison to be case-insensitive by @hktitof in #7993
- caddytls: synchronize storage cleaner with TLS.Stop via context and WaitGroup by @jum in #7954
- httpcaddyfile: give each adaptation its own directive order by @faiyazrahmann in #7995
- listen: don't wedge a reloaded listener sharing a socket on Windows by @alexandre-daubois in #7999
- caddyhttp: demote Alt-Svc ErrNoAltSvcPort to debug level by @jum in #8000
- events: skip dispatch setup when nothing is subscribed by @u5surf in #7997
- reverseproxy: replace only known placeholders in health check body by @IslamElsayed in #8003
- acmeserver: say when the CA database is locked by another process by @faiyazrahmann in #8007
- fix: require module path boundaries when matching packages by @wangjingshuiku in #7957
- caddyhttp: preserve unknown placeholders in static response headers by @hktitof in #8014
- build(deps): bump google.golang.org/grpc from 1.82.1 to 1.83.1 by @dependabot[bot] in #7984
- reverseproxy: propagate TCP half-close on upgraded streams by @btncwn in #8027
- build(deps): bump google.golang.org/grpc from 1.83.1 to 1.83.2 by @dependabot[bot] in #8028
- caddyfile: Expand imports inside named routes by @XiaoleC05 in #7986
- chore(deps): bump cel-go and switch import to new url by @techknowlogick in #8030
- core: synchronize Stop with concurrent config reloads by @H-XX-D in #8038
- reverse_proxy with versions 3 silently ignores tls_trust_pool and verifies against the system roots by @tippexs in #8042
- admin: fix warnings and status code in /load API by @amirdaraby in #7267
- fastcgi: explain 411 responses for unknown-length bodies by @Rohilalala in #7956
- Move websocket header normalization later by @nfreya in #7921
- chore: remove AI moderator workflow by @mohammed90 in #8059
- caddyhttp: surface 413 for oversized request body placeholders by @hktitof in #7969
- reverseproxy: Fix partial response not flushed to clients by @WeidiDeng in #7849
- caddyhttp: fix randString sameCase dictionary to match its docs by @mohammed90 in #7972
- requestbody: replace only known placeholders in the set body by @hktitof in #8008
- internal: fix
MaxSizeSubjectsListForLogoff-by-one whenmaxToDisplayis0by @mohammed90 in #7970 - caddyauth: only replace known placeholders in basic auth credentials by @hktitof in #8017
- map: Distinguish duplicate literal and regexp inputs by @Indra55 in #8067
- map: reject malformed destination placeholders by @sleet0922 in #8074
- httpcaddyfile: new
tls_automate_namesglobal option by @IslamElsayed in #8015 - build: bump all dependencies - (Go 1.26 floor) by @steadytao in #8056
- caddyhttp: wait for servers from previous configs on exit by @dunglas in #8009
- caddyhttp: use errors.AsType for request body limit errors by @mholt in #8090
- ci: pin cosign to v2 for release signing by @francislavoie in #8092
New Contributors
- @vijayvenkatj made their first contribution in #7800
- @AliMickey made their first contribution in #7802
- @bluegate-studio made their first contribution in #7809
- @alhudz made their first contribution in #7812
- @oksusucha made their first contribution in #7810
- @luccinmasirika made their first contribution in #7817
- @larrasket made their first contribution in #7827
- @Dean2026 made their first contribution in #7826
- @yintaisha made their first contribution in #7832
- @TowyTowy made their first contribution in #7869
- @thientd made their first contribution in #7858
- @PichuChen made their first contribution in #7884
- @futurehua made their first contribution in #7892
- @Salynn made their first contribution in #7886
- @SillyZir made their first contribution in #7898
- @jvoisin made their first contribution in #7847
- @firefart made their first contribution in #7903
- @renich made their first contribution in #7912
- @r0h1tb made their first contribution in #7922
- @ousamabenyounes made their first contribution in #7924
- @ittakestwo123 made their first contribution in #7940
- @bzyy1024 made their first contribution in #7934
- @faiyazrahmann made their first contribution in #7958
- @gautamrizwani made their first contribution in #7894
- @0jaspahwa made their first contribution in #7960
- @AmariahAK made their first contribution in #7910
- @DavidCarliez made their first contribution in #7952
- @kojah made their first contribution in #7968
- @SashaMIT made their first contribution in #7988
- @hktitof made their first contribution in #7993
- @IslamElsayed made their first contribution in #8003
- @wangjingshuiku made their first contribution in #7957
- @btncwn made their first contribution in #8027
- @XiaoleC05 made their first contribution in #7986
- @H-XX-D made their first contribution in #8038
- @tippexs made their first contribution in #8042
- @amirdaraby made their first contribution in #7267
- @Rohilalala made their first contribution in #7956
- @nfreya made their first contribution in #7921
- @Indra55 made their first contribution in #8067
Full Changelog: v2.11.4...v2.11.6