Skip to content

caddy

v2.11.9
caddyserver/caddy

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Version of caddyserver/caddy you run

CompareView on GitHub

Supported versions

Source: endoflife.date
LineLatestReleasedSecurity fixes
22.11.9May 4, 2020Supported
11.0.5Apr 24, 2019Ended 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_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)
    • 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 env values now take precedence over request headers, so a client can no longer override HTTP_* variables you set with env (see below). (#8358)
  • v2.11.8Nov 4, 2026

    :warning: Breaking changes

    • 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)
    • 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)
    • 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 normalized path (repeated slashes merged, . segments removed); REQUEST_URI is unchanged.
    • A try_files / file candidate ending in / only matches a directory, so /foo.txt/ no longer matches the file /foo.txt. (#8326)
    • 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 @transformable. Malformed forms like no-cache; no-transform no 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 expression line in the same matcher set (combine them with &&) (#8183)
    • unknown TLS curves or protocol_min / protocol_max names, like TLS1.3 instead of tls1.3, which used to be silently ignored (#8266)
    • a repeated sni_regexp in a TLS policy match block (#8137)
    • ip_mask log filter sizes out of range, like 33 for IPv4 (#8241)
    • basic_auth usernames that end up identical after placeholder expansion (#8315)
    • ports longer than 11 characters (#8277)
    • 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)
  • 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 the max_header_size server 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_idle and write_idle in the timeouts server option, or per-route with the timeouts directive. (#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 new expected_dot_headers server option.
    • A wildcard site's client_auth no longer applies to more specific hostnames that have their own site blocks. For example, public.example.com no longer inherits mTLS from *.example.com. If you were counting on that inheritance, configure client_auth on 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_auth uri (#7814)
    • invalid weighted_round_robin weights (#7807)
    • a non-integer browse file_limit (#7988)
    • duplicate or ambiguous map inputs (#8067)
    • malformed map destination placeholders (#8074)
    • module paths with ambiguous @ version separators (#7974)
    • Admin API /load now returns 400 with warnings inside a valid JSON body when a config is invalid. Before, it returned 200 with two concatenated JSON objects. (#7267)
    • method matcher values are normalized to uppercase, so method get now matches GET requests. (#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 email global 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 acme issuer with your email filled out, and the ca field set to ZeroSSL's ACME server URL. If you want redundancy with Let's Encrypt, be sure to specify another acme issuer as well (defaults OK, but we recommend setting an email there too).
    • The zerossl issuer 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 the acme module 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_deprecated DNS provider module. It has been deprecated for 4 years. Use caddy-dns modules 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 ask option in the JSON has been deprecated in favor of a permission module (Caddyfile unchanged) (#6055), and Caddyfile support for permission modules 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 basicauth Caddyfile directive has been renamed to basic_auth (#6092), and skip_log has been renamed to log_skip. The old names will continue to work for now, with a deprecation warning in the logs. (#6066).
    • The basic_auth handler no longer supports scrypt (deprecated for nearly two years) (#6091)
    • The forwarded option has been deprecated for a long time and has now been removed from the remote_ip matcher. Use the client_ip matcher instead. (#6085)
    • Reverse proxy: The buffer_requests, buffer_responses, and max_buffer_size settings have been removed after being deprecated for 14 months. Use request_buffers and response_buffers instead 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 GitHub
BadgeLatest version of caddyserver/caddy[![version](/badge/caddyserver/caddy)](/p/caddyserver/caddy)

This 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 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 through a trailing-slash URL. A try_files pattern 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_format option for file_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_robin selection. 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_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)
  • 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 env values now take precedence over request headers, so a client can no longer override HTTP_* variables you set with env (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 (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, 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 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)
  • httpcaddyfile: A stricter client_auth could be silently dropped when merging TLS connection policies (see above). Thanks @sam-kota. (#8306)
  • Updated go-jose to 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

Full Changelog: https://github.com/caddyserver/caddy/compare/v2.11.8...v2.11.9

Full release notesCompare with previous