aio-libs/aiohttp · error · WSServerHandshakeError

Invalid upgrade header

Error message

Invalid upgrade header

What it means

Raised as WSServerHandshakeError when the response UPGRADE header (case-insensitive) is not 'websocket'. The server returned 101 but did not confirm the WebSocket upgrade, so the protocol is not actually established.

Solutions

  1. Inspect resp.headers in the caught WSServerHandshakeError to see what was sent.
  2. Fix the server/proxy to respond with exactly 'Upgrade: websocket'.
  3. If you cannot change the server, do not use ws_connect against it; speak raw HTTP/1.1 or pick a compliant endpoint.

Example fix

// before
# server returns 'Upgrade: h2c' on a 101
await session.ws_connect('wss://x')  # raises Invalid upgrade header
// after
# fix the server to send 'Upgrade: websocket'
Defensive patterns

Strategy: try-catch

Validate before calling

async def preflight_upgrade(session, url):
    # Verify the server's handshake behavior before relying on it.
    async with session.get(url) as r:
        return r.headers.get('Upgrade', '').lower()

Type guard

def upgrade_is_websocket(header_value: str) -> bool:
    return header_value.strip().lower() == 'websocket'

Try / catch

from aiohttp import WSServerHandshakeError

try:
    ws = await session.ws_connect(url)
except WSServerHandshakeError as e:
    if e.message == 'Invalid upgrade header':
        # server/proxy is non-RFC-compliant; cannot use ws_connect
        raise
    raise

Prevention

When it happens

Trigger: Server replies 101 with Upgrade: h2c or Upgrade: HTTP/2 or omits/misspells the UPGRADE header. resp.headers.get('Upgrade','').lower() != 'websocket'.

Common situations: Reverse proxy or framework that auto-responds 101 for any Upgrade but writes a different token. Custom server returning Upgrade: WebSocket with extra tokens or different casing/value. Bug in a hand-rolled WS server.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/e467c089f5f61da8. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/client.py:1110

            proxy=proxy,
            ssl=ssl,
            server_hostname=server_hostname,
            proxy_headers=proxy_headers,
        )

        try:
            # check handshake
            if resp.status != 101:
                raise WSServerHandshakeError(
                    resp.request_info,
                    resp.history,
                    message="Invalid response status",
                    status=resp.status,
                    headers=resp.headers,
                )

            if resp.headers.get(hdrs.UPGRADE, "").lower() != "websocket":
                raise WSServerHandshakeError(
                    resp.request_info,
                    resp.history,
                    message="Invalid upgrade header",
                    status=resp.status,
                    headers=resp.headers,
                )

            if not resp._upgraded:
                raise WSServerHandshakeError(
                    resp.request_info,
                    resp.history,
                    message="Invalid connection header",
                    status=resp.status,
                    headers=resp.headers,
                )

            # key calculation
            r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "")

View on GitHub (pinned to d041d4d0fd)