Jump to solution
Verify

The Fix

Upgrade to version 0.20.0 or later.

Based on closed Kludex/uvicorn issue #1778 · PR/commit linked

Production note: Most teams hit this during upgrades or environment changes. Roll out with a canary and smoke critical endpoints (health, OpenAPI/docs) before 100%.

Jump to Verify Open PR/Commit
@@ -90,7 +90,7 @@ connecting IPs in the `forwarded-allow-ips` configuration. !!! note - The `--no-server-header` flag doesn't have effect on the WebSockets implementations. + The `--no-date-header` flag doesn't have effect on the `websockets` implementation.
repro.py
INFO: Started server process [31158] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:9000 (Press CTRL+C to quit) DEBUG: = connection is CONNECTING DEBUG: < GET / HTTP/1.1 DEBUG: < host: localhost:9000 DEBUG: < upgrade: websocket DEBUG: < connection: Upgrade DEBUG: < sec-websocket-key: ncSN1OGo9LCGhQIV+O8ukg== DEBUG: < sec-websocket-version: 13 DEBUG: < sec-websocket-extensions: permessage-deflate; client_max_window_bits DEBUG: < sec-websocket-protocol: ocpp2.0.1 DEBUG: < user-agent: Python/3.10 websockets/10.4 2022-11-26 13:19:18.635 | DEBUG | uvicorn_websockets_example.asgi:handler:92 - scope={'type': 'websocket', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'scheme': 'ws', 'server': ('127.0.0.1', 9000), 'client': ('127.0.0.1', 54220), 'root_path': '', 'path': '/', 'raw_path': b'/', 'query_string': b'', 'headers': [(b'host', b'localhost:9000'), (b'upgrade', b'websocket'), (b'connection', b'Upgrade'), (b'sec-websocket-key', b'ncSN1OGo9LCGhQIV+O8ukg=='), (b'sec-websocket-version', b'13'), (b'sec-websocket-extensions', b'permessage-deflate; client_max_window_bits'), (b'sec-websocket-protocol', b'ocpp2.0.1'), (b'user-agent', b'Python/3.10 websockets/10.4')], 'subprotocols': ['ocpp2.0.1']} 2022-11-26 13:19:18.635 | DEBUG | uvicorn_websockets_example.asgi:handler:95 - event={'type': 'websocket.connect'} INFO: ('127.0.0.1', 54220) - "WebSocket /" [accepted] DEBUG: > HTTP/1.1 101 Switching Protocols DEBUG: > Upgrade: websocket DEBUG: > Connection: Upgrade DEBUG: > Sec-WebSocket-Accept: RTByXq2XHmPDpnv6NpdnP12aqEs= DEBUG: > Sec-WebSocket-Extensions: permessage-deflate DEBUG: > Sec-WebSocket-Protocol: ocpp2.0.1 DEBUG: > date: Sat, 26 Nov 2022 11:19:17 GMT DEBUG: > server: uvicorn DEBUG: > sec-websocket-protocol: ocpp2.0.1, ocpp2.0, ocpp1.6 INFO: connection open DEBUG: = connection is OPEN DEBUG: ! failing connection with code 1006 DEBUG: = connection is CLOSED DEBUG: x half-closing TCP connection 2022-11-26 13:19:18.636 | DEBUG | uvicorn_websockets_example.asgi:handler:95 - event={'type': 'websocket.disconnect', 'code': 1006} INFO: connection closed
verify
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
fix.md
Option A — Upgrade to fixed release\nUpgrade to version 0.20.0 or later.\nWhen NOT to use: This fix should not be applied if the application requires custom handling of WebSocket headers.\n\n

Why This Fix Works in Production

  • Trigger: = connection is CONNECTING
  • Mechanism: Uvicorn sends both 'Sec-WebSocket-Protocol' and 'sec-websocket-protocol' headers, causing conflicts
  • Why the fix works: Adds default headers to WebSocket implementations, addressing issues with the Sec-WebSocket-Protocol header being sent incorrectly. (first fixed release: 0.20.0).
Production impact:
  • If left unfixed, the same config can fail only in production (env differences), causing startup failures or partial feature outages.

Why This Breaks in Prod

  • Shows up under Python 3.10.2 in real deployments (not just unit tests).
  • Uvicorn sends both 'Sec-WebSocket-Protocol' and 'sec-websocket-protocol' headers, causing conflicts
  • Surfaces as: = connection is CONNECTING

Proof / Evidence

  • GitHub issue: #1778
  • Fix PR: https://github.com/encode/uvicorn/pull/1606
  • First fixed release: 0.20.0
  • Reproduced locally: No (not executed)
  • Last verified: 2026-02-09
  • Confidence: 0.85
  • Did this fix it?: Yes (upstream fix exists)
  • Own content ratio: 0.30

Discussion

High-signal excerpts from the issue thread (symptoms, repros, edge-cases).

“This seems to be an issue in the latest 0.20.0 release. In 0.19.0 (and also in 0.18.3) there are no extra 'sec-websocket-protocol: ocpp2.0.1, ocpp2.0, ocpp1.6'…”
@villekr · 2022-11-27 · source
“@villekr What happens on the wsproto implementation? We should make use of the protocols parameter from websockets.”
@Kludex · 2022-11-26 · source
“I tested the example with 'wsproto' (on the asgi server side) and the result is identical. Example code is also updated to have ws="wsproto" argument…”
@villekr · 2022-11-26 · source
“It was probably introduced on https://github.com/encode/uvicorn/pull/1606.”
@Kludex · 2022-11-27 · source

Failure Signature (Search String)

  • = connection is CONNECTING

Error Message

Stack trace
error.txt
Error Message ------------- = connection is CONNECTING GET / HTTP/1.1 Host: localhost:9000 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: uU1gy8fzdEP9Hf4z1lnsGQ== Sec-WebSocket-Version: 13 Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits Sec-WebSocket-Protocol: ocpp2.0.1 User-Agent: Python/3.10 websockets/10.4 < HTTP/1.1 101 Switching Protocols < Upgrade: websocket < Connection: Upgrade < Sec-WebSocket-Accept: Lfxa3NBPkfrDN87wlkv1oAtLS+k= < Sec-WebSocket-Extensions: permessage-deflate < Sec-WebSocket-Protocol: ocpp2.0.1 < date: Sat, 26 Nov 2022 11:27:07 GMT < server: uvicorn < sec-websocket-protocol: ocpp2.0.1, ocpp2.0, ocpp1.6 ! failing connection with code 1006 x half-closing TCP connection = connection is CLOSED Traceback (most recent call last): File ".../lib/python3.10/site-packages/websockets/legacy/client.py", line 258, in process_subprotocol raise InvalidHandshake(f"multiple subprotocols: {subprotocols}") websockets.exceptions.InvalidHandshake: multiple subprotocols: ocpp2.0.1, ocpp2.0.1, ocpp2.0, ocpp1.6

Minimal Reproduction

repro.py
INFO: Started server process [31158] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:9000 (Press CTRL+C to quit) DEBUG: = connection is CONNECTING DEBUG: < GET / HTTP/1.1 DEBUG: < host: localhost:9000 DEBUG: < upgrade: websocket DEBUG: < connection: Upgrade DEBUG: < sec-websocket-key: ncSN1OGo9LCGhQIV+O8ukg== DEBUG: < sec-websocket-version: 13 DEBUG: < sec-websocket-extensions: permessage-deflate; client_max_window_bits DEBUG: < sec-websocket-protocol: ocpp2.0.1 DEBUG: < user-agent: Python/3.10 websockets/10.4 2022-11-26 13:19:18.635 | DEBUG | uvicorn_websockets_example.asgi:handler:92 - scope={'type': 'websocket', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'scheme': 'ws', 'server': ('127.0.0.1', 9000), 'client': ('127.0.0.1', 54220), 'root_path': '', 'path': '/', 'raw_path': b'/', 'query_string': b'', 'headers': [(b'host', b'localhost:9000'), (b'upgrade', b'websocket'), (b'connection', b'Upgrade'), (b'sec-websocket-key', b'ncSN1OGo9LCGhQIV+O8ukg=='), (b'sec-websocket-version', b'13'), (b'sec-websocket-extensions', b'permessage-deflate; client_max_window_bits'), (b'sec-websocket-protocol', b'ocpp2.0.1'), (b'user-agent', b'Python/3.10 websockets/10.4')], 'subprotocols': ['ocpp2.0.1']} 2022-11-26 13:19:18.635 | DEBUG | uvicorn_websockets_example.asgi:handler:95 - event={'type': 'websocket.connect'} INFO: ('127.0.0.1', 54220) - "WebSocket /" [accepted] DEBUG: > HTTP/1.1 101 Switching Protocols DEBUG: > Upgrade: websocket DEBUG: > Connection: Upgrade DEBUG: > Sec-WebSocket-Accept: RTByXq2XHmPDpnv6NpdnP12aqEs= DEBUG: > Sec-WebSocket-Extensions: permessage-deflate DEBUG: > Sec-WebSocket-Protocol: ocpp2.0.1 DEBUG: > date: Sat, 26 Nov 2022 11:19:17 GMT DEBUG: > server: uvicorn DEBUG: > sec-websocket-protocol: ocpp2.0.1, ocpp2.0, ocpp1.6 INFO: connection open DEBUG: = connection is OPEN DEBUG: ! failing connection with code 1006 DEBUG: = connection is CLOSED DEBUG: x half-closing TCP connection 2022-11-26 13:19:18.636 | DEBUG | uvicorn_websockets_example.asgi:handler:95 - event={'type': 'websocket.disconnect', 'code': 1006} INFO: connection closed

Environment

  • Python: 3.10.2

What Broke

WebSocket connections fail due to multiple subprotocol headers being sent.

Why It Broke

Uvicorn sends both 'Sec-WebSocket-Protocol' and 'sec-websocket-protocol' headers, causing conflicts

Fix Options (Details)

Option A — Upgrade to fixed release Safe default (recommended)

Upgrade to version 0.20.0 or later.

When NOT to use: This fix should not be applied if the application requires custom handling of WebSocket headers.

Use when you can deploy the upstream fix. It is usually lower-risk than long-lived workarounds.

Fix reference: https://github.com/encode/uvicorn/pull/1606

First fixed release: 0.20.0

Last verified: 2026-02-09. Validate in your environment.

Get updates

We publish verified fixes weekly. No spam.

Subscribe

When NOT to Use This Fix

  • This fix should not be applied if the application requires custom handling of WebSocket headers.

Verify Fix

verify
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.

Did This Fix Work in Your Case?

Quick signal helps us prioritize which fixes to verify and improve.

Prevention

  • Add a TLS smoke test that performs a real handshake in CI (include CA bundle validation and hostname checks).
  • Alert on handshake failures by error string and endpoint to catch cert/CA changes quickly.

Version Compatibility Table

VersionStatus
0.20.0 Fixed

Related Issues

No related fixes found.

Sources

We don’t republish the full GitHub discussion text. Use the links above for context.