caddy
v2.11.9Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
- Maintenance
- ActiveBased on the last push: commit history is not collected yet
- License
- Apache-2.0Permissive · OSI-approved
- Security
- 23 advisories8 high, 14 medium, 1 low · Scorecard 8.3/10
- Supported versions
- 1 line supportednewest 2 · 1 ended
79.1k stars · 5.3k forks · 275 open issues · last release 10 days ago · data checked 4 hours ago
Supported versions
Source: endoflife.date| Line | Latest | Released | Security fixes |
|---|---|---|---|
| 2 | 2.11.9 | May 4, 2020 | Supported |
| 1 | 1.0.5 | Apr 24, 2019 | Ended Jul 1, 2020 |
Breaking changes
What to check before upgrading, taken from the notes of the last 30 releases.
- v2.11.9Nov 5, 2026
:warning: Breaking changes
- Conflicting
client_authis now a Caddyfile error. When two site blocks share TLS connection policies for the same hostname but set differentclient_auth, the adapter used to keep the first one and silently drop the other, which could quietly remove a stricter mTLS requirement. (#8306) - FastCGI requests that are ambiguous about which script they run get
400: - 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) - on Windows, script paths with an 8.3 short name component (like
ADMINI~1) or a:(NTFS alternate data streams) (#8356) - FastCGI
envvalues now take precedence over request headers, so a client can no longer overrideHTTP_*variables you set withenv(see below). (#8358)
- Conflicting
- v2.11.8Nov 4, 2026
:warning: Breaking changes
- Requests with an invalid or dots-only host (like
..) are rejected with400on every protocol, before any route runs. Before, HTTP/2 and HTTP/3 also accepted hosts containing/or\. (#8328) request_bodymax_sizenow rejects a request with413as soon as itsContent-Lengthis over the limit, even if nothing would have read the body. (#8337)php_fastcgisites without anindex.php: requests for paths that don't exist are now sent to PHP asindex.php, as the documentedfirst_exist_fallbacktry policy always promised, instead of falling through tofile_serverand returning404. Also,PATH_INFOandDOCUMENT_URIare now derived from the normalized path (repeated slashes merged,.segments removed);REQUEST_URIis unchanged.- A
try_files/filecandidate ending in/only matches a directory, so/foo.txt/no longer matches the file/foo.txt. (#8326) encodenow compresses responses even when the *request* hasCache-Control: no-transform(a response'sno-transformis still honored), and responses that could have been encoded always getVary: Accept-Encoding. To keep the old behavior:@transformable not header Cache-Control *no-transform*andencode @transformable. Malformed forms likeno-cache; no-transformno longer disable compression. (#8301, #8025)- Stricter config validation. Some configs that were silently accepted (and didn't do what you'd expect) are now errors:
- more than one
expressionline in the same matcher set (combine them with&&) (#8183) - unknown TLS
curvesorprotocol_min/protocol_maxnames, likeTLS1.3instead oftls1.3, which used to be silently ignored (#8266) - a repeated
sni_regexpin a TLS policymatchblock (#8137) ip_masklog filter sizes out of range, like33for IPv4 (#8241)basic_authusernames that end up identical after placeholder expansion (#8315)- ports longer than 11 characters (#8277)
- Evaluation order now follows the Caddyfile for
handle_errorsblocks with status codes and forvarswith non-path matchers. If you had overlapping codes or set the same variable twice, a different block or value may now win. (#8195, #8197)
- Requests with an invalid or dots-only host (like
- v2.11.6Oct 1, 2026
:warning: 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:
- duplicate
named_routes(#7800) - duplicate
forward_authuri(#7814) - invalid
weighted_round_robinweights (#7807) - a non-integer
browsefile_limit(#7988) - duplicate or ambiguous
mapinputs (#8067) - malformed
mapdestination placeholders (#8074) - module paths with ambiguous
@version separators (#7974) - 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)
- v2.8.0May 29, 2024
:warning: Breaking changes:
- ZeroSSL (#6229) (this is _one_ overall change, but requires some explanation):
- Up to now, Caddy used both Let's Encrypt and ZeroSSL by default to get certificates without any configuration. In 2.8, this is changing slightly. Due to upcoming changes to ZeroSSL accounting policies, ZeroSSL now requires your email address to be able to access their free ACME endpoint.
- As such, Caddy will only implicitly add the ZeroSSL issuer to your config if you provide an email address in your Caddyfile using the
emailglobal option. (We have already recommended this for years.) If you already do this, you don't have to make any changes and you'll still get Let's Encrypt and ZeroSSL automatically as defaults. - If you use JSON to configure certificate automation policies, you will need to ensure you use the
acmeissuer with youremailfilled out, and thecafield set to ZeroSSL's ACME server URL. If you want redundancy with Let's Encrypt, be sure to specify anotheracmeissuer as well (defaults OK, but we recommend setting an email there too). - The
zerosslissuer module is no longer ACME-capable and is now exclusively for the ZeroSSL API. An API key from your ZeroSSL account is required. (The ZeroSSL ACME server can still be used with theacmemodule pointed to ZeroSSL's ACME server. You can provide your account email and/or EAB as well.) If you were using the ZeroSSL issuer with an API key, it will now start using ZeroSSL's API, which was probably the expected behavior anyways. The API has several advantages over the ACME endpoint, but may require payment: - Faster response times
- IP certificates
- Management tools in your ZeroSSL account dashboard
- Technical support
- To clarify, Let's Encrypt is still a default issuer even if you don't provide an email address (but we have always strongly recommended to do so).
- You can still use ZeroSSL's ACME endpoint with your own External Account Binding (EAB) credentials.
- See notes in #6229 for some examples and further explanations.
- Removed support for the
lego_deprecatedDNS provider module. It has been deprecated for 4 years. Usecaddy-dnsmodules instead; there are over 50 to choose from already. They are more flexible, compile much leaner, and are easier to implement and support. If yours is not supported it can be easily implemented. Sponsors at or above the Business tier can request to have their provider implemented for free. - On-demand TLS: The
askoption in the JSON has been deprecated in favor of a permission module (Caddyfile unchanged) (#6055), and Caddyfile support forpermissionmodules is added (6a02999) - Admin API:
Etag(used for concurrency control) is now a header, not a trailer. This is less efficient, but still virtually no clients properly implement trailer support. - For consistency, the
basicauthCaddyfile directive has been renamed tobasic_auth(#6092), andskip_loghas been renamed tolog_skip. The old names will continue to work for now, with a deprecation warning in the logs. (#6066). - The
basic_authhandler no longer supportsscrypt(deprecated for nearly two years) (#6091) - The
forwardedoption has been deprecated for a long time and has now been removed from theremote_ipmatcher. Use theclient_ipmatcher instead. (#6085) - Reverse proxy: The
buffer_requests,buffer_responses, andmax_buffer_sizesettings have been removed after being deprecated for 14 months. Userequest_buffersandresponse_buffersinstead if you need buffering. - Go API: If you called
caddy.Context.AppIfConfigured(), it now returns an error, as part of a bug fix. (#6292)
Release history
All releases on GitHubThis is a quick follow-up on top of 2.11.8, to fix a regression it introduced and a few more security issues, mostly in FastCGI. It contains only fixes backported from master, plus a couple of small additions. If you're on 2.11.8 and serve PHP, we recommend upgrading.
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.
Highlights
- Fixed: 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 itsindex.php, so it never reached PHP and usually got a 404. This broke WordPress admin pages and other apps that serve an index through a trailing-slash URL. Atry_filespattern now only matches a directory when it ends in/. Thanks @dunglas! (#8352) - Path 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) - New
plain_time_formatoption forfile_server browse: sets the time layout of plain-text directory listings (Accept: text/plain). Thanks @steffenbusch! (#8084) - Fewer allocations in path cleaning, header rewriting and
weighted_round_robinselection. Thanks @jvoisin! (#7842) - For plugin authors: new
httpcaddyfile.Helper.IsNamedRoute()tells a directive whether it's being parsed inside a named route. (#8354)
:warning: Breaking changes
- Conflicting
client_authis now a Caddyfile error. When two site blocks share TLS connection policies for the same hostname but set differentclient_auth, the adapter used to keep the first one and silently drop the other, which could quietly remove a stricter mTLS requirement. (#8306) - FastCGI requests that are ambiguous about which script they run get
400: - 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) - on Windows, script paths with an 8.3 short name component (like
ADMINI~1) or a:(NTFS alternate data streams) (#8356) - FastCGI
envvalues now take precedence over request headers, so a client can no longer overrideHTTP_*variables you set withenv(see below). (#8358)
Security fixes
Thank you to everyone who reported responsibly and helped with patches:
- fastcgi: A client could override any
HTTP_*variable set with the FastCGI transport's (orphp_fastcgi's)envsubdirective by sending a matching request header, for example choosing the user an application sees inHTTP_X_AUTH_USER. Configured values now win. Reported by @JebeenLee and @Camel4, fixed by @dunglas. (GHSA-q34h-28rj-5hjm, #8358) - 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)
- fastcgi: Encoded slashes and dot segments could make a
%-containing path matcher approve one script while another one runs. Thanks @dunglas. (#8357) - fastcgi: Over HTTP/2, a
hostfield sent next to:authoritycould make the application see a different host (HTTP_HOST) than the one Caddy routed the request to. Thanks @dunglas. (#8355) - httpcaddyfile: A stricter
client_authcould be silently dropped when merging TLS connection policies (see above). Thanks @sam-kota. (#8306) - Updated
go-joseto v4.1.6 for GHSA-mgcc-cww5-fp2q. (#8299)
:warning: These security patches may be breaking if your application relies on the buggy behaviors.
:rotating_light: 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.
What's Changed
- caddyhttp: lower-case path patterns with placeholders at request time by @BenMacha in https://github.com/caddyserver/caddy/pull/8343
- httpcaddyfile: reject conflicting client_auth when merging TLS policies by @sam-kota in https://github.com/caddyserver/caddy/pull/8306
- build(deps): bump the actions-deps group across 1 directory with 10 updates by @dependabot[bot] in https://github.com/caddyserver/caddy/pull/8340
- build(deps): bump github.com/go-jose/go-jose/v4 from 4.1.5 to 4.1.6 by @dependabot[bot] in https://github.com/caddyserver/caddy/pull/8299
- fileserver: make plain-text browse time format configurable by @steffenbusch in https://github.com/caddyserver/caddy/pull/8084
- fileserver: only match a dir when the pattern ends by / by @dunglas in https://github.com/caddyserver/caddy/pull/8352
- httpcaddyfile: add Helper.IsNamedRoute by @dunglas in https://github.com/caddyserver/caddy/pull/8354
- fastcgi: take HTTP_HOST from the routed host only by @dunglas in https://github.com/caddyserver/caddy/pull/8355
- fastcgi: reject Win 8.3 aliases and alt data streams paths by @dunglas in https://github.com/caddyserver/caddy/pull/8356
- caddyhttp: presize buffers in hot paths to reduce allocations by @jvoisin in https://github.com/caddyserver/caddy/pull/7842
- fastcgi: let configured env variables override request headers by @dunglas in https://github.com/caddyserver/caddy/pull/8358
- fastcgi: reject paths whose escaped form selects another script by @dunglas in https://github.com/caddyserver/caddy/pull/8357
Full Changelog: https://github.com/caddyserver/caddy/compare/v2.11.8...v2.11.9