denoland/deno · error · TypeError
Invalid Header: 'connection' header must contain 'Upgrade'
Error message
Invalid Header: 'connection' header must contain 'Upgrade'
What it means
Second handshake check in `Deno.upgradeWebSocket`: the `Connection` header's comma-separated values must include the token `Upgrade` (case-insensitive), because RFC 6455 requires the client to opt in with `Connection: Upgrade`. A missing or rewritten header throws a TypeError before the socket is created.
Solutions
- Configure the proxy to pass the token: `proxy_set_header Connection "upgrade"`
- Ensure clients use a real WebSocket implementation (browser WebSocket API or a ws library)
- Check both `upgrade` and `connection` headers before calling `upgradeWebSocket`, and answer 400 otherwise
Example fix
// before
const ws = Deno.upgradeWebSocket(request);
// after
const conn = request.headers.get("connection") ?? "";
if (!/upgrade/i.test(conn)) {
return new Response("Connection: Upgrade required", { status: 400 });
}
const ws = Deno.upgradeWebSocket(request); Defensive patterns
Strategy: validation
Validate before calling
const conn = request.headers.get("connection") ?? "";
const hasUpgradeToken = conn.split(",").some((v) => v.trim().toLowerCase() === "upgrade");
if (!hasUpgradeToken) return new Response("Connection: Upgrade required", { status: 400 });
const ws = Deno.upgradeWebSocket(request); Type guard
const connectionHasUpgrade = (req: Request): boolean =>
(req.headers.get("connection") ?? "")
.split(",")
.some((v) => v.trim().toLowerCase() === "upgrade"); Try / catch
try {
return Deno.upgradeWebSocket(request);
} catch (err) {
if (err instanceof TypeError && err.message.includes("connection")) {
return new Response("Bad handshake", { status: 400 });
}
throw err;
} Prevention
- Do not terminate WebSocket traffic behind HTTP/2-only frontends (Connection header is illegal in h2)
- Verify proxy config passes the Connection header token through
- Test the full handshake with a real WebSocket client, not plain HTTP tools
When it happens
Trigger: Intermediaries that rewrite `Connection: keep-alive, Upgrade` to plain `keep-alive`; HTTP/2 requests (which forbid the Connection header entirely); hand-rolled clients sending only `Upgrade: websocket` without the Connection token.
Common situations: Reverse proxies and WAFs normalizing hop-by-hop headers; API gateways terminating WebSocket-incompatible frontends; testing with HTTP/2-enabled tooling.
Related errors
- Invalid Header: 'sec-websocket-key' header must be set
- Invalid Header: 'upgrade' header must contain 'websocket'
- Already closed
- Already upgraded
- ERR_HTTP_HEADERS_SENT
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/f0897b0aeef6606e.
Report an issue: GitHub.
Appendix: source
Thrown at ext/http/02_websocket.ts:56
function upgradeWebSocket(request, options = { __proto__: null }) {
const inner = toInnerRequest(request);
if (inner._wantsUpgrade) {
inner._throwIfUpgraded();
}
const upgrade = request.headers.get("upgrade");
const upgradeHasWebSocketOption = upgrade !== null &&
websocketCvf(upgrade);
if (!upgradeHasWebSocketOption) {
throw new TypeError(
"Invalid Header: 'upgrade' header must contain 'websocket'",
);
}
const connection = request.headers.get("connection");
const connectionHasUpgradeOption = connection !== null &&
upgradeCvf(connection);
if (!connectionHasUpgradeOption) {
throw new TypeError(
"Invalid Header: 'connection' header must contain 'Upgrade'",
);
}
const websocketKey = request.headers.get("sec-websocket-key");
if (websocketKey === null) {
throw new TypeError(
"Invalid Header: 'sec-websocket-key' header must be set",
);
}
const accept = op_http_websocket_accept_header(websocketKey);
const r = newInnerResponse(101);
r.headerList = [
["upgrade", "websocket"],
["connection", "Upgrade"],
["sec-websocket-accept", accept],View on GitHub (pinned to 9ad36f7a2c)