Skip to content

Update dependency gunicorn to v26 - #56

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/gunicorn-26.x
Open

Update dependency gunicorn to v26#56
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/gunicorn-26.x

Conversation

@renovate

@renovate renovate Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
gunicorn (changelog) ==25.3.0==26.2.0 age confidence

Release Notes

benoitc/gunicorn (gunicorn)

v26.2.0: gunicorn 26.2.0

Compare Source

Cleartext HTTP/2 lands, and an HTTP/2 security fix.

Cleartext HTTP/2 (h2c)

http2_cleartext accepts prior-knowledge, upgrade, both or off (the
default). Prior knowledge serves a connection that opens with the HTTP/2
preface; upgrade honours an HTTP/1.1 Upgrade: h2c request. Both work on the
gthread, gevent and asgi workers.

This is for deployments where TLS is terminated by a proxy that speaks HTTP/2
upstream, so the hop into gunicorn no longer drops to HTTP/1.1. Only peers in
forwarded_allow_ips are considered; everyone else is served HTTP/1.x exactly
as if the setting were off. Each mechanism is enabled separately, so turning one
on does not turn the other on.

Do not expose a cleartext HTTP/2 port to the internet.

Security

HTTP2Request built its headers straight from the stream, so nothing the HTTP/1
path enforces applied over HTTP/2: the underscore and header_map policy,
duplicate Host and Content-Type, control characters in values, and the
forwarded_allow_ips trust gate. An untrusted client could set SCRIPT_NAME
and forge HTTP_* entries in the WSGI environ, and decide wsgi.url_scheme
through :scheme. Both request classes now share one policy mixin, and the
scheme comes from the transport.

If you serve HTTP/2, this is the reason to upgrade.

Other HTTP/2 fixes

WSGI responses were buffered whole before anything was sent; they stream now.
HEAD, 204 and 304 no longer carry a body. Events read while blocked on a
flow-control window were discarded, losing requests and body data outright.
sendfile() is refused on HTTP/2 responses rather than bypassing framing.

Request bodies dropped on Upgrade requests

On the ASGI worker with the fast parser, any request carrying an Upgrade
header reached the application with an empty body, whatever the header's value
and with HTTP/2 switched off entirely. Fixed in gunicorn_h1c 0.6.9, which the
fast extra now requires.

Full changelog: https://gunicorn.org/news/

v26.1.0: gunicorn 26.1.0

Compare Source

New Features
  • Glob patterns in reload_extra_files: entries containing *, ? or [
    are treated as patterns, so ui/*/config.json watches every view's config
    without listing them one by one. Patterns are re-expanded on every reload
    check rather than once at startup, so a file created later starts being
    watched without restarting gunicorn, and ** recurses. A pattern matching
    nothing warns instead of failing, since with live expansion it may match later
    (#​1643,
    #​3662).
Security
  • Dependency floors raised past known advisories: every declared floor was
    checked against the advisory database. tornado, h2, setuptools and
    pymdown-extensions permitted vulnerable versions and now require the first
    clean release; pytest and httpx were unpinned and now carry floors. The
    tornado example pinned tornado<6, which was both the source of several
    advisories and older than the >=6.5.0 the tornado worker needs, so the
    example could not run as pinned.
Bug Fixes
  • SIGHUP did not reload the logger configuration: Arbiter.reload()
    re-read the configuration file but kept using the logger built at startup,
    calling only reopen_files() on its existing handlers. Changes to
    logconfig, logconfig_dict, logconfig_json and loglevel were ignored
    until a full restart, which in containers meant replacing the pod. The
    existing logger now re-runs its setup on reload, so new handlers, formats
    and levels take effect while the process identity and its listeners are
    preserved, and re-running the setup no longer stacks duplicate syslog
    handlers. An invalid log configuration on reload is not fatal either: the
    error is reported on stderr, the previous working configuration is restored
    and the master keeps running with it
    (#​3353).

  • Truncated chunked bodies accepted: RFC 9112 section 7.1.2 ends a chunked
    body with 0 CRLF CRLF, the second CRLF being the mandatory empty trailer
    section. ChunkedReader.parse_chunk_size() swallowed the NoMoreData raised
    while scanning for it, so a body cut short right after the last chunk line was
    treated as complete instead of rejected. It now raises
    ChunkMissingTerminator
    (#​3382,
    #​3685).

  • --spew crashed on dynamically generated code: the trace hook indexed the
    2-tuple returned by inspect.getsourcelines() by line number rather than
    indexing the list of lines, so a frame with no __file__ raised
    AttributeError: 'int' object has no attribute 'rstrip' on line 1 and
    IndexError beyond it. The tuple is now unpacked and offset by the source's
    starting line (#​3344,
    #​3495).

  • Duplicate Host and Content-Type headers accepted: RFC 9110 section 5.3
    allows only one of each, and a repeat cannot be merged into a list, so the
    message means different things to gunicorn and to anything downstream. Both
    are now rejected with InvalidHeader. The check lives in the policy hook
    shared by both parsers, so the pure-Python and fast parsers agree. Duplicate
    Content-Length was already rejected and is unchanged
    (#​3366,
    #​3548).

  • Non-worker children reported as failed workers: reap_workers() reaps
    every child through waitpid(-1), including processes the kernel reparented
    onto gunicorn when it runs as PID 1 in a container, but it logged the exit
    status before checking whether the pid was ever a worker. An unrelated process
    produced Worker (pid:N) exited with code M and triggered alerts. More
    seriously, such a process exiting with code 3 or 4 raised HaltServer and shut
    the server down. Ownership is now established first: the dirty arbiter is
    reported as itself, unknown children are reaped silently at debug level, and
    only real workers can halt the server
    (#​3220,
    #​3566).

  • Dirty arbiter exits were invisible on SIGCHLD: handle_chld() called
    reap_workers() first, whose waitpid(-1) claimed the dirty arbiter before
    reap_dirty_arbiter() could identify it, so the latter always hit ECHILD and
    its reporting never ran. The dirty arbiter is now reaped first, and
    reap_workers() recognises it if it exits mid-loop.

  • Dirty arbiter returned stale responses after a worker timeout: when a
    request reached dirty_timeout the arbiter answered the client with a timeout
    error but kept the worker connection open. The worker's late response was then
    the first message waiting on that socket, so the next request routed to the
    same worker received the previous request's result, and every request after it
    stayed one response behind. The connection is now closed on timeout, so the
    late answer is discarded with it
    (#​3626).

  • ASGI connection count leaked on server-initiated close: nr_conns was
    only decremented in connection_lost(), behind a guard keyed on the same
    flag _close_transport() sets first. Every close the server started (a
    Connection: close response, a keepalive timeout, an error abort) leaked one
    count, so ASGIWorker._shutdown() ran the full graceful_timeout and warned
    about connections that were already gone. The guard now uses its own flag, so
    the decrement and the rest of the cleanup run exactly once whichever side
    closes first (#​3661).

  • Inotify reloader on cwd-relative extra files: reload_extra_files entries
    with no directory part (for example .env) produced an empty dirname, and
    watching it raised InotifyError with ENOENT. The current directory is now
    watched as . (#​3377,
    #​3667).

  • StatsD zero-valued metrics: gauges, counters, histograms and timers
    reporting 0 were silently dropped because the value was tested for
    truthiness. Only None is skipped now
    (#​3676).

  • Spurious no-body warning from sendfile(): a HEAD, 204 or 304 response
    served through sendfile() warned about dropped body bytes even when the
    file was empty and nothing was dropped. It now warns only when there are
    bytes to drop, matching write()
    (#​3684).

  • Bare except in the gevent websocket example: narrowed to
    except Exception (#​3683).

  • ASGI receive() cancellation: Let asyncio.CancelledError propagate
    from BodyReceiver instead of swallowing it and returning
    http.disconnect. Frameworks that cancel their disconnect listener after
    the response completes (Django) no longer see the cancel masked, so
    request_finished fires and close_old_connections() runs. Fixes idle
    database connections leaking since 25.1.0
    (#​3627,
    #​3654).

  • Control socket leak on SIGHUP reload: The control thread is now marked
    ready once its loop and server are live, and the stop paths wait on that
    readiness before scheduling shutdown. Reloads no longer leak one thread and
    its selector fd plus unix socket per worker, which eventually raised
    "too many open files"
    (#​3648).

  • WSGI body framing on HEAD/1xx/204/304: Mirror the ASGI strip-and-warn
    behavior on the WSGI path. Content-Length is stripped on 1xx/204 per
    RFC 9110 section 6.4.2, body bytes are dropped for no-body responses in
    both write() and sendfile(), and a single warning is logged per request
    (#​3413).

Refactoring
  • Pass log arguments to the logger instead of pre-formatting the worker
    termination message in Arbiter.reap_workers()
    (#​3678).
Changes
  • packaging is no longer a runtime dependency: it was only ever imported by
    the gevent worker, to compare gevent's version. It moved to the gevent and
    testing extras, so a plain pip install gunicorn pulls in nothing
    (#​3643).

  • Fast HTTP Parser: Require gunicorn_h1c >= 0.6.6, which rejects duplicate
    Host and Content-Type headers in the C parser itself. Gunicorn already
    refuses them on both the WSGI and ASGI paths, so this changes nothing that is
    reachable; it moves the rejection to where the bytes are read and lets the
    ASGI corpus exercise those cases against the fast parser directly.

Full changelog: https://gunicorn.org/2026-news/

v26.0.0

Compare Source

Breaking Changes

  • Eventlet worker removed: The eventlet worker class has been dropped. Migrate to gevent, gthread, or tornado.

New Features

  • ASGI Framework Compatibility Suite: New end-to-end compatibility test harness covering Starlette, FastAPI, Litestar, Quart, Sanic, and BlackSheep. Current grid passes 438/444 tests (98%).
  • ASGI Test Suite Expansion: 134 additional ASGI unit tests covering protocol semantics, lifespan, websockets, and chunked framing.

Security

  • HTTP/1.1 Request-Target Validation (RFC 9112 sections 3.2.3, 3.2.4):
    • Reject authority-form request-target outside CONNECT
    • Reject asterisk-form request-target outside OPTIONS
    • Reject relative-reference request-targets
  • Header Field Hardening (RFC 9110):
    • Reject control characters in header field-value (section 5.5)
    • Reject forbidden trailer field-names (section 6.5.1)
    • Reject Content-Length list form (RFC 9112 section 6.3)
  • Request Smuggling Hardening:
    • Tighten keepalive gate and scope finish_body byte cap
    • Keep _body_receiver alive across the keepalive smuggling gate so pipelined requests cannot re-enter a closed body
    • Address parser/protocol findings from a six-point WSGI/ASGI audit
  • PROXY Protocol (ASGI): Enforce proxy_allow_ips and tighten v1/v2 parsing in the ASGI callback parser.
  • Connection Draining: Drain the connection on close per RFC 9112 section 9.6 to prevent reset-on-close truncation.

Bug Fixes

  • Body Framing on HEAD/204/304:
    • Keep Content-Length on HEAD and 304 responses (#​3621)
    • Drop body framing on HEAD/204/304 even when the framework set it
    • Warn once when an ASGI app emits a body for a no-body response
  • HTTP/2 ASGI:
    • Fix _handle_stream_ended to set _body_complete in the async HTTP/2 handler so request bodies finalize correctly on stream end
    • Add InvalidChunkExtension mapping and fast-parser support in ASGI tests (#​3565)
  • HTTP/1.1 100-Continue: Stop adding Transfer-Encoding: chunked to 100-Continue interim responses.
  • WebSocket Close Handshake (RFC 6455):
    • Comply with the close handshake state machine
    • Close the transport after the close handshake completes
    • Fix binary send when the text key is None
  • Early Hints: Validate headers in the early_hints callback to match process_headers; pass only the header name to InvalidHeader (#​3588).
  • ASGI Framework Fixes:
    • Fix ASGI disconnect handling for Django-style apps
    • Fix Litestar request handling (use raw ASGI receive for body/headers)
    • Fix Litestar HTTP endpoints for compatibility tests
    • Fix Quart headers endpoint to normalize keys to lowercase
    • Fix Quart WebSocket close test app (missing accept())
    • Fix duplicate Transfer-Encoding header for BlackSheep streaming

Refactoring

  • Split BodyReceiver._closed into separate transport and body-wait flags for clearer keepalive/EOF semantics.

Changes

  • Fast HTTP Parser: Require gunicorn_h1c >= 0.6.5. Drop the last python_only test markers; the C extension is now used wherever available (CPython only; PyPy continues to use the Python parser).
  • Test Dependencies: Add h2 and uvloop to the testing extra; remove eventlet.
  • Docker Build: Bump GitHub Actions docker/setup-qemu-action, docker/setup-buildx-action, docker/login-action, docker/build-push-action, and docker/metadata-action to current major versions.

Full changelog: benoitc/gunicorn@25.3.0...26.0.0


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate
renovate Bot force-pushed the renovate/gunicorn-26.x branch from 991cd0a to 28a056d Compare May 18, 2026 18:06
@renovate
renovate Bot force-pushed the renovate/gunicorn-26.x branch from 28a056d to 326e517 Compare June 11, 2026 11:48
@renovate
renovate Bot force-pushed the renovate/gunicorn-26.x branch from 326e517 to 7d81fa2 Compare July 20, 2026 19:05
@renovate
renovate Bot force-pushed the renovate/gunicorn-26.x branch from 7d81fa2 to 8ab4289 Compare July 30, 2026 15:04
@renovate
renovate Bot force-pushed the renovate/gunicorn-26.x branch from 8ab4289 to 4ad58c5 Compare August 21, 2026 17:15
@renovate
renovate Bot force-pushed the renovate/gunicorn-26.x branch from 4ad58c5 to b0f2bfa Compare August 27, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants