<span id="news"></span>
# Changelog

## 25.3.0 - 2026-03-26

### Bug Fixes

- **HTTP/2 ASGI Body Duplication**: Fix request body being received twice in HTTP/2
  ASGI requests, causing JSON parsing errors with "Extra data" messages
  ([#3558](https://github.com/benoitc/gunicorn/issues/3558))

- **ASGI Chunked EOF Handling**: Add `finish()` method to callback parser to handle
  chunked encoding edge case where connection closes before final CRLF after zero-chunk

- **HTTP/2 Documentation**: Fix `http_protocols` examples to use comma-separated string
  instead of list syntax ([#3561](https://github.com/benoitc/gunicorn/issues/3561))

- **Chunked Encoding**: Reject chunk extensions containing bare CR bytes per RFC 9112
  ([#3556](https://github.com/benoitc/gunicorn/discussions/3556))

- **Request Line Limit**: Fix `--limit-request-line 0` to mean unlimited as documented,
  instead of using default maximum. Works with both Python and fast C parser.
  ([#3563](https://github.com/benoitc/gunicorn/issues/3563))

### Security

- **ASGI Parser Header Validation**: Add security checks per RFC 9110/9112:
  - Reject duplicate Content-Length headers
  - Reject requests with both Content-Length and Transfer-Encoding
  - Reject chunked transfer encoding in HTTP/1.0
  - Reject stacked chunked encoding
  - Validate Transfer-Encoding values
  - Strict chunk size validation

### Changes

- **Fast HTTP Parser**: Update to gunicorn_h1c >= 0.6.3 for `asgi_headers` property
  and `InvalidChunkExtension` validation for bare CR rejection

- **ASGI PROXY Protocol**: Add PROXY protocol v1/v2 support to callback parser

- **Docker Images**: Update to Python 3.14

---

## 25.2.0 - 2026-03-24

### New Features

- **Fast HTTP Parser (gunicorn_h1c 0.6.0)**: Integrate new exception types and limit
  parameters from gunicorn_h1c 0.6.0 for both WSGI and ASGI workers
  - Requires gunicorn_h1c >= 0.6.0 for `http_parser='fast'`
  - Falls back to Python parser in `auto` mode if version not met
  - Proper HTTP status codes for limit errors (414, 431)

### Bug Fixes

- **uWSGI Async Workers**: Fix `InvalidUWSGIHeader: incomplete header` error
  when using gevent or gthread workers with uwsgi protocol behind nginx.
  ([#3552](https://github.com/benoitc/gunicorn/issues/3552),
  [PR #3554](https://github.com/benoitc/gunicorn/pull/3554))

- **FileWrapper Iterator Protocol**: Add `__iter__` and `__next__` methods to
  `FileWrapper` for full PEP 3333 compliance. Previously only supported old-style
  `__getitem__` iteration which broke code explicitly using `iter()` or `next()`.
  ([#3396](https://github.com/benoitc/gunicorn/issues/3396),
  [PR #3550](https://github.com/benoitc/gunicorn/pull/3550))

### Performance

- **ASGI HTTP Parser Optimizations**: Improve ASGI worker HTTP parsing performance
  - Callback-based parsing with direct `bytearray` buffer operations
  - Use `bytearray.find()` directly instead of converting to bytes first
  - Use index-based iteration for header parsing instead of `list.pop(0)` (O(1) vs O(n))

---

## 25.1.0 - 2026-02-13

### New Features

- **Control Interface (gunicornc)**: Add interactive control interface for managing
  running Gunicorn instances, similar to birdc for BIRD routing daemon
  ([PR #3505](https://github.com/benoitc/gunicorn/pull/3505))
  - Unix socket-based communication with JSON protocol
  - Interactive mode with readline support and command history
  - Commands: `show all/workers/dirty/config/stats/listeners`
  - Worker management: `worker add/remove/kill`, `dirty add/remove`
  - Server control: `reload`, `reopen`, `shutdown`
  - New settings: `--control-socket`, `--control-socket-mode`, `--no-control-socket`
  - New CLI tool: `gunicornc` for connecting to control socket
  - See [Control Interface Guide](guides/gunicornc.md) for details

- **Dirty Stash**: Add global shared state between workers via `dirty.stash`
  ([PR #3503](https://github.com/benoitc/gunicorn/pull/3503))
  - In-memory key-value store accessible by all workers
  - Supports get, set, delete, clear, keys, and has operations
  - Useful for sharing state like feature flags, rate limits, or cached data

- **Dirty Binary Protocol**: Implement efficient binary protocol for dirty arbiter IPC
  using TLV (Type-Length-Value) encoding
  ([PR #3500](https://github.com/benoitc/gunicorn/pull/3500))
  - More efficient than JSON for binary data
  - Supports all Python types: str, bytes, int, float, bool, None, list, dict
  - Better performance for large payloads

- **Dirty TTIN/TTOU Signals**: Add dynamic worker scaling for dirty arbiters
  ([PR #3504](https://github.com/benoitc/gunicorn/pull/3504))
  - Send SIGTTIN to increase dirty workers
  - Send SIGTTOU to decrease dirty workers
  - Respects minimum worker constraints from app configurations

### Changes

- **ASGI Worker**: Promoted from beta to stable
- **Dirty Arbiters**: Now marked as beta feature

### Documentation

- Fix Markdown formatting in /configure documentation

---

## 25.0.3 - 2026-02-07

### Bug Fixes

- Fix RuntimeError when StopIteration is raised inside ASGI response body
  coroutine (PEP 479 compliance)

- Fix deprecation warning for passing maxsplit as positional argument in
  `re.split()` (Python 3.13+)

---

## 25.0.2 - 2026-02-06

### Bug Fixes

- Fix ASGI concurrent request failures through nginx proxy by normalizing
  sockaddr tuples to handle both 2-tuple (IPv4) and 4-tuple (IPv6) formats
  ([PR #3485](https://github.com/benoitc/gunicorn/pull/3485))

- Fix graceful disconnect handling for ASGI worker to properly handle
  client disconnects without raising exceptions
  ([PR #3485](https://github.com/benoitc/gunicorn/pull/3485))

- Fix lazy import of dirty module for gevent compatibility - prevents
  import errors when concurrent.futures is imported before gevent monkey-patching
  ([PR #3483](https://github.com/benoitc/gunicorn/pull/3483))

### Changes

- Refactor: Extract `_normalize_sockaddr` utility function for consistent
  socket address handling across workers

- Add license headers to all Python source files

- Update copyright year to 2026 in LICENSE and NOTICE files

---

## 25.0.1 - 2026-02-02

### Bug Fixes

- Fix ASGI streaming responses (SSE) hanging: add chunked transfer encoding for
  HTTP/1.1 responses without Content-Length header. Without chunked encoding,
  clients wait for connection close to determine end-of-response.

### Changes

- Update celery_alternative example to use FastAPI with native ASGI worker and
  uvloop for async task execution

### Testing

- Add ASGI compliance test suite with Docker-based integration tests covering HTTP,
  WebSocket, streaming, lifespan, framework integration (Starlette, FastAPI),
  HTTP/2, and concurrency scenarios

---

## 25.0.0 - 2026-02-01

### New Features

- **Dirty Arbiters**: Separate process pool for executing long-running, blocking
  operations (AI model loading, heavy computation) without blocking HTTP workers
  ([PR #3460](https://github.com/benoitc/gunicorn/pull/3460))
  - Inspired by Erlang's dirty schedulers
  - Asyncio-based with Unix socket IPC
  - Stateful workers that persist loaded resources
  - New settings: `--dirty-app`, `--dirty-workers`, `--dirty-timeout`,
    `--dirty-threads`, `--dirty-graceful-timeout`
  - Lifecycle hooks: `on_dirty_starting`, `dirty_post_fork`,
    `dirty_worker_init`, `dirty_worker_exit`

- **Per-App Worker Allocation for Dirty Arbiters**: Control how many dirty workers
  load each app for memory optimization with heavy models
  ([PR #3473](https://github.com/benoitc/gunicorn/pull/3473))
  - Set `workers` class attribute on DirtyApp (e.g., `workers = 2`)
  - Or use config format `module:class:N` (e.g., `myapp:HeavyModel:2`)
  - Requests automatically routed to workers with the target app
  - New exception `DirtyNoWorkersAvailableError` for graceful error handling
  - Example: 8 workers × 10GB model = 80GB → with `workers=2`: 20GB (75% savings)

- **HTTP/2 Support (Beta)**: Native HTTP/2 (RFC 7540) support for improved performance
  with modern clients ([PR #3468](https://github.com/benoitc/gunicorn/pull/3468))
  - Multiplexed streams over a single connection
  - Header compression (HPACK)
  - Flow control and stream prioritization
  - Works with gthread, gevent, and ASGI workers
  - New settings: `--http-protocols`, `--http2-max-concurrent-streams`,
    `--http2-initial-window-size`, `--http2-max-frame-size`, `--http2-max-header-list-size`
  - Requires SSL/TLS and h2 library: `pip install gunicorn[http2]`
  - See [HTTP/2 Guide](guides/http2.md) for details
  - New example: `examples/http2_gevent/` with Docker and tests

- **HTTP 103 Early Hints**: Support for RFC 8297 Early Hints to enable browsers to
  preload resources before the final response
  ([PR #3468](https://github.com/benoitc/gunicorn/pull/3468))
  - WSGI: `environ['wsgi.early_hints'](headers)` callback
  - ASGI: `http.response.informational` message type
  - Works with both HTTP/1.1 and HTTP/2

- **uWSGI Protocol for ASGI Worker**: The ASGI worker now supports receiving requests
  via the uWSGI binary protocol from nginx
  ([PR #3467](https://github.com/benoitc/gunicorn/pull/3467))

### Bug Fixes

- Fix HTTP/2 ALPN negotiation for gevent and eventlet workers when
  `do_handshake_on_connect` is False (the default). The TLS handshake is now
  explicitly performed before checking `selected_alpn_protocol()`.

- Fix setproctitle initialization with systemd socket activation
  ([#3465](https://github.com/benoitc/gunicorn/issues/3465))

- Fix `Expect: 100-continue` handling: ignore the header for HTTP/1.0 requests
  since 100-continue is only valid for HTTP/1.1+
  ([PR #3463](https://github.com/benoitc/gunicorn/pull/3463))

- Fix missing `_expected_100_continue` attribute in UWSGIRequest

- Disable setproctitle on macOS to prevent segfaults during process title updates

- Publish full exception traceback when the application fails to load
  ([#3462](https://github.com/benoitc/gunicorn/issues/3462))

- Fix ASGI: quick shutdown on SIGINT/SIGQUIT, graceful on SIGTERM

### Deprecations

- **Eventlet Worker**: The `eventlet` worker is deprecated and will be removed in
  Gunicorn 26.0. Eventlet itself is [no longer actively maintained](https://eventlet.readthedocs.io/en/latest/asyncio/migration.html).
  Please migrate to `gevent`, `gthread`, or another supported worker type.

### Changes

- Remove obsolete Makefile targets
  ([PR #3471](https://github.com/benoitc/gunicorn/pull/3471))

---

## 24.1.1 - 2026-01-24

### Bug Fixes

- Fix `forwarded_allow_ips` and `proxy_allow_ips` to remain as strings for backward
  compatibility with external tools like uvicorn. Network validation now uses strict
  mode to detect invalid CIDR notation (e.g., `192.168.1.1/24` where host bits are set)
  ([#3458](https://github.com/benoitc/gunicorn/issues/3458),
  [PR #3459](https://github.com/benoitc/gunicorn/pull/3459))

---

## 24.1.0 - 2026-01-23

### New Features

- **Official Docker Image**: Gunicorn now publishes official Docker images to GitHub
  Container Registry at `ghcr.io/benoitc/gunicorn`
  - Based on Python 3.12 slim image
  - Uses recommended worker formula (2 × CPU + 1)
  - Configurable via environment variables

- **PROXY Protocol v2 Support**: Extended PROXY protocol implementation to support
  the binary v2 format in addition to the existing text-based v1 format
  - New `--proxy-protocol` modes: `off`, `v1`, `v2`, `auto`
  - Works with HAProxy, AWS NLB/ALB, and other PROXY protocol v2 sources

- **CIDR Network Support**: `--forwarded-allow-ips` and `--proxy-allow-from` now
  accept CIDR notation (e.g., `192.168.0.0/16`) for specifying trusted networks

- **Socket Backlog Metric**: New `gunicorn.socket.backlog` gauge metric reports
  the current socket backlog size on Linux systems

- **InotifyReloader Enhancement**: The inotify-based reloader now watches newly
  imported modules, not just those loaded at startup

### Bug Fixes

- Fix signal handling regression where SIGCLD alias caused errors on Linux
- Fix socket blocking mode on keepalive connections with async workers
- Handle `SSLWantReadError` in `finish_body()` to prevent worker hangs
- Log SIGTERM as info level instead of warning
- Print exception details to stderr when worker fails to boot
- Fix `unreader.unread()` to prepend data to buffer instead of appending
- Prevent `RecursionError` when pickling Config objects

---

## 24.0.0 - 2026-01-23

### New Features

- **ASGI Worker (Beta)**: Native asyncio-based ASGI support for running async Python
  frameworks like FastAPI, Starlette, and Quart without external dependencies
  - HTTP/1.1 with keepalive connections
  - WebSocket support
  - Lifespan protocol for startup/shutdown hooks
  - Optional uvloop for improved performance

- **uWSGI Binary Protocol**: Support for receiving requests from nginx via
  `uwsgi_pass` directive

- **Documentation Migration**: Migrated to MkDocs with Material theme

### Security

- **eventlet**: Require eventlet >= 0.40.3 (CVE-2021-21419, CVE-2025-58068)
- **gevent**: Require gevent >= 24.10.1 (CVE-2023-41419, CVE-2024-3219)
- **tornado**: Require tornado >= 6.5.0 (CVE-2025-47287)

---

## 23.0.0 - 2024-08-10

- minor docs fixes ([PR #3217](https://github.com/benoitc/gunicorn/pull/3217), [PR #3089](https://github.com/benoitc/gunicorn/pull/3089), [PR #3167](https://github.com/benoitc/gunicorn/pull/3167))
- worker_class parameter accepts a class ([PR #3079](https://github.com/benoitc/gunicorn/pull/3079))
- fix deadlock if request terminated during chunked parsing ([PR #2688](https://github.com/benoitc/gunicorn/pull/2688))
- permit receiving Transfer-Encodings: compress, deflate, gzip ([PR #3261](https://github.com/benoitc/gunicorn/pull/3261))
- permit Transfer-Encoding headers specifying multiple encodings. note: no parameters, still ([PR #3261](https://github.com/benoitc/gunicorn/pull/3261))
- sdist generation now explicitly excludes sphinx build folder ([PR #3257](https://github.com/benoitc/gunicorn/pull/3257))
- decode bytes-typed status (as can be passed by gevent) as utf-8 instead of raising `TypeError` ([PR #2336](https://github.com/benoitc/gunicorn/pull/2336))
- raise correct Exception when encounting invalid chunked requests ([PR #3258](https://github.com/benoitc/gunicorn/pull/3258))
- the SCRIPT_NAME and PATH_INFO headers, when received from allowed forwarders, are no longer restricted for containing an underscore ([PR #3192](https://github.com/benoitc/gunicorn/pull/3192))
- include IPv6 loopback address ``[::1]`` in default for [forwarded-allow-ips](reference/settings.md#forwarded_allow_ips) and [proxy-allow-ips](reference/settings.md#proxy_allow_ips) ([PR #3192](https://github.com/benoitc/gunicorn/pull/3192))

!!! note
    - The SCRIPT_NAME change mitigates a regression that appeared first in the 22.0.0 release
    - Review your [forwarded-allow-ips](reference/settings.md#forwarded_allow_ips) setting if you are still not seeing the SCRIPT_NAME transmitted
    - Review your [forwarder-headers](reference/settings.md#forwarder_headers) setting if you are missing headers after upgrading from a version prior to 22.0.0


### Breaking changes

- refuse requests where the uri field is empty ([PR #3255](https://github.com/benoitc/gunicorn/pull/3255))
- refuse requests with invalid CR/LR/NUL in heade field values ([PR #3253](https://github.com/benoitc/gunicorn/pull/3253))
- remove temporary ``--tolerate-dangerous-framing`` switch from 22.0 ([PR #3260](https://github.com/benoitc/gunicorn/pull/3260))
- If any of the breaking changes affect you, be aware that now refused requests can post a security problem, especially so in setups involving request pipe-lining and/or proxies.

## 22.0.0 - 2024-04-17

- use `utime` to notify workers liveness
- migrate setup to pyproject.toml
- fix numerous security vulnerabilities in HTTP parser (closing some request smuggling vectors)
- parsing additional requests is no longer attempted past unsupported request framing
- on HTTP versions < 1.1 support for chunked transfer is refused (only used in exploits)
- requests conflicting configured or passed SCRIPT_NAME now produce a verbose error
- Trailer fields are no longer inspected for headers indicating secure scheme
- support Python 3.12

### Breaking changes

- minimum version is Python 3.7
- the limitations on valid characters in the HTTP method have been bounded to Internet Standards
- requests specifying unsupported transfer coding (order.md) are refused by default (rare.md)
- HTTP methods are no longer casefolded by default (IANA method registry contains none affected)
- HTTP methods containing the number sign (#) are no longer accepted by default (rare.md)
- HTTP versions < 1.0 or >= 2.0 are no longer accepted by default (rare, only HTTP/1.1 is supported)
- HTTP versions consisting of multiple digits or containing a prefix/suffix are no longer accepted
- HTTP header field names Gunicorn cannot safely map to variables are silently dropped, as in other software
- HTTP headers with empty field name are refused by default (no legitimate use cases, used in exploits)
- requests with both Transfer-Encoding and Content-Length are refused by default (such a message might indicate an attempt to perform request smuggling)
- empty transfer codings are no longer permitted (reportedly seen with really old & broken proxies)


### Security

- fix CVE-2024-1135

## History

- [2026](2026-news.md)
- [2024](2024-news.md)
- [2023](2023-news.md)
- [2021](2021-news.md)
- [2020](2020-news.md)
- [2019](2019-news.md)
- [2018](2018-news.md)
- [2017](2017-news.md)
- [2016](2016-news.md)
- [2015](2015-news.md)
- [2014](2014-news.md)
- [2013](2013-news.md)
- [2012](2012-news.md)
- [2011](2011-news.md)
- [2010](2010-news.md)
