denoland/deno · error · TypeError

Invalid Header: 'sec-websocket-key' header must be set

Error message

Invalid Header: 'sec-websocket-key' header must be set

What it means

RFC 6455 requires the client to send a random `Sec-WebSocket-Key`; the server hashes it to produce `Sec-WebSocket-Accept`. `Deno.upgradeWebSocket` reads it via `request.headers.get('sec-websocket-key')` and throws this TypeError when it is absent, because the accept header — and therefore a valid 101 response — cannot be computed.

Solutions

  1. Use a spec-compliant WebSocket client (browser WebSocket, or a ws library)
  2. Fix the proxy/WAF to forward all `Sec-WebSocket-*` headers
  3. Validate the handshake headers up front and answer 400 for incomplete ones

Example fix

// before
const ws = Deno.upgradeWebSocket(request);

// after
if (request.headers.get("sec-websocket-key") === null) {
  return new Response("Missing Sec-WebSocket-Key", { status: 400 });
}
const ws = Deno.upgradeWebSocket(request);
Defensive patterns

Strategy: validation

Validate before calling

if (request.headers.get("sec-websocket-key") === null) {
  return new Response("Missing Sec-WebSocket-Key", { status: 400 });
}
const ws = Deno.upgradeWebSocket(request);

Type guard

const hasWebSocketKey = (req: Request): boolean =>
  req.headers.has("sec-websocket-key");

Try / catch

try {
  return Deno.upgradeWebSocket(request);
} catch (err) {
  if (err instanceof TypeError && err.message.includes("sec-websocket-key")) {
    return new Response("Incomplete WebSocket handshake", { status: 400 });
  }
  throw err;
}

Prevention

When it happens

Trigger: Non-browser clients (raw TCP scripts, custom embedded clients, curl) that send Upgrade/Connection headers but no key; intermediaries stripping `Sec-*` headers; replayed or crafted requests.

Common situations: Load balancers or WAFs dropping unrecognized `Sec-WebSocket-*` headers; IoT/embedded clients with incomplete handshake implementations; test harnesses issuing partial handshakes.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/934a66b8449aaeba. Report an issue: GitHub.

Appendix: source

Thrown at ext/http/02_websocket.ts:63

    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],
  ];

  const protocolsStr = request.headers.get("sec-websocket-protocol") || "";
  const protocols = StringPrototypeSplit(protocolsStr, ", ");
  if (protocols && options.protocol) {
    if (ArrayPrototypeIncludes(protocols, options.protocol)) {
      ArrayPrototypePush(r.headerList, [

View on GitHub (pinned to 9ad36f7a2c)