Skip to content

# 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)

- 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)

- Chunked Encoding: Reject chunk extensions containing bare CR bytes per RFC 9112 (#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)

### 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, PR #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, PR #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)
- 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 for details

- Dirty Stash: Add global shared state between workers via `dirty.stash` (PR #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)

- 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)

- 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)

- Fix graceful disconnect handling for ASGI worker to properly handle client disconnects without raising exceptions (PR #3485)

- Fix lazy import of dirty module for gevent compatibility - prevents import errors when concurrent.futures is imported before gevent monkey-patching (PR #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)
- 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)

- 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)

- 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 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)

- 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)

### 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)

- 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)

- 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)

- 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. Please migrate to `gevent`, `gthread`, or another supported worker type.

### Changes

- Remove obsolete Makefile targets (PR #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, PR #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, PR #3089, PR #3167)
- worker_class parameter accepts a class (PR #3079)
- fix deadlock if request terminated during chunked parsing (PR #2688)
- permit receiving Transfer-Encodings: compress, deflate, gzip (PR #3261)
- permit Transfer-Encoding headers specifying multiple encodings. note: no parameters, still (PR #3261)
- sdist generation now explicitly excludes sphinx build folder (PR #3257)
- decode bytes-typed status (as can be passed by gevent) as utf-8 instead of raising `TypeError` (PR #2336)
- raise correct Exception when encounting invalid chunked requests (PR #3258)
- the SCRIPT_NAME and PATH_INFO headers, when received from allowed forwarders, are no longer restricted for containing an underscore (PR #3192)
- include IPv6 loopback address `[::1]` in default for forwarded-allow-ips and proxy-allow-ips (PR #3192)

Note

- The SCRIPT_NAME change mitigates a regression that appeared first in the 22.0.0 release
- Review your forwarded-allow-ips setting if you are still not seeing the SCRIPT_NAME transmitted
- Review your 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)
- refuse requests with invalid CR/LR/NUL in heade field values (PR #3253)
- remove temporary `--tolerate-dangerous-framing` switch from 22.0 (PR #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
- 2024
- 2023
- 2021
- 2020
- 2019
- 2018
- 2017
- 2016
- 2015
- 2014
- 2013
- 2012
- 2011
- 2010