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 must contain the token 'Upgrade' (case-insensitive comma list, via upgradeCvf). This is part of RFC 6455's handshake (Connection: Upgrade); its absence means the request is not a protocol upgrade request.

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 89f33cbef2)

Solutions

  1. Guard before upgrading: parse req.headers.get('connection') and require the 'upgrade' token (case-insensitive) before calling Deno.upgradeWebSocket.
  2. Configure the proxy to set Connection: upgrade when forwarding (nginx: proxy_set_header Connection "upgrade").
  3. Use a spec-compliant WebSocket client library rather than hand-written handshake code.

Example fix

// before
const { response } = Deno.upgradeWebSocket(req); // Connection: keep-alive -> throws

// after
const conn = req.headers.get("connection") ?? "";
if (!conn.toLowerCase().split(',').map((s) => s.trim()).includes("upgrade")) {
  return new Response("not a websocket upgrade", { status: 400 });
}
const { socket, response } = Deno.upgradeWebSocket(req);
Defensive patterns

Strategy: validation

Validate before calling

const connectionTokens = (req.headers.get("connection") ?? "")
  .toLowerCase().split(",").map((s) => s.trim());
if (!connectionTokens.includes("upgrade")) {
  return new Response("Connection: Upgrade required", { status: 400 });
}

Type guard

function hasConnectionUpgrade(req: Request): boolean { return (req.headers.get("connection") ?? "").toLowerCase().split(",").map((s) => s.trim()).includes("upgrade"); }

Try / catch

try { return Deno.upgradeWebSocket(req).response; } catch (e) { if (e instanceof TypeError && e.message.includes("'connection' header")) { return new Response("expected Connection: Upgrade", { status: 400 }); } throw e; }

Prevention

When it happens

Trigger: Handshake requests where a client or intermediary sent Upgrade: websocket but Connection: keep-alive/close; proxies that forward one hop header but rewrite Connection; hand-crafted HTTP clients that set only the upgrade header; HTTP/2 pseudo-upgrade attempts mapped onto HTTP/1 semantics without the header.

Common situations: Misconfigured reverse proxies or CDNs that drop the Connection header (hop-by-hop header, must be explicitly re-set); custom embedded HTTP clients; API gateways terminating WS.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/f0897b0aeef6606e. Report an issue: GitHub.