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%.
@@ -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.
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
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
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).
- 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 What happens on the wsproto implementation? We should make use of the protocols parameter from websockets.”
“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…”
“It was probably introduced on https://github.com/encode/uvicorn/pull/1606.”
Failure Signature (Search String)
- = connection is CONNECTING
Error Message
Stack trace
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
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.
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.
When NOT to Use This Fix
- This fix should not be applied if the application requires custom handling of WebSocket headers.
Verify Fix
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
| Version | Status |
|---|---|
| 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.