aio-libs/aiohttp · error · WSServerHandshakeError

Invalid connection header

Error message

Invalid connection header

What it means

Raised as WSServerHandshakeError when resp._upgraded is False, i.e. the Connection header did not include 'upgrade' (case-insensitive). Even with status 101 and Upgrade: websocket, the server must also signal Connection: Upgrade to complete RFC 6455.

Solutions

  1. Inspect the caught WSServerHandshakeError .headers to confirm what Connection value was received.
  2. Fix the server/proxy to send 'Connection: Upgrade'.
  3. Bypass intermediaries that rewrite hop-by-hop headers, or use wss:// end-to-end.

Example fix

// before
# server returns 101, Upgrade: websocket, but Connection: close
await session.ws_connect('wss://x')  # raises Invalid connection header
// after
# fix the server to send 'Connection: Upgrade'
Defensive patterns

Strategy: try-catch

Validate before calling

# Server-side prevention:
# response_headers['Connection'] = 'Upgrade'
# response_headers['Upgrade'] = 'websocket'
# No client-side validation avoids this; the server must comply.

Type guard

def connection_includes_upgrade(header_value: str) -> bool:
    return 'upgrade' in [v.strip().lower() for v in (header_value or '').split(',')]

Try / catch

from aiohttp import WSServerHandshakeError

try:
    ws = await session.ws_connect(url)
except WSServerHandshakeError as e:
    if e.message == 'Invalid connection header':
        # check e.headers for Connection; fix server/proxy and retry
        raise
    raise

Prevention

When it happens

Trigger: Server replies 101, Upgrade: websocket, but Connection: close (or no Connection header). resp._upgraded is determined from the Connection header and is False.

Common situations: Reverse proxy that strips or rewrites the Connection header. Hand-rolled server that sets Upgrade but forgets Connection. Intermediaries (load balancers, CDNs) rewriting hop-by-hop headers.

Related errors


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

Appendix: source

Thrown at aiohttp/client.py:1119

                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, "")
            match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode()
            if r_key != match:
                raise WSServerHandshakeError(
                    resp.request_info,
                    resp.history,
                    message="Invalid challenge response",
                    status=resp.status,
                    headers=resp.headers,
                )

View on GitHub (pinned to d041d4d0fd)