denoland/deno · error · TypeError

Invalid Header: 'upgrade' header must contain 'websocket'

Error message

Invalid Header: 'upgrade' header must contain 'websocket'

What it means

`Deno.upgradeWebSocket(request)` performs the RFC 6455 server-side handshake, and the first thing it validates is the `Upgrade` header: its comma-separated values must include `websocket` (case-insensitive). A request without it is an ordinary HTTP request, not a WebSocket handshake, so the upgrade is refused with a TypeError before any WebSocket object is created.

Solutions

  1. Gate the call: only upgrade when `request.headers.get('upgrade')` includes 'websocket'
  2. Configure the proxy: `proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"`
  3. Return a normal Response (e.g. 400) for non-WebSocket requests

Example fix

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

// after
if (!/websocket/i.test(request.headers.get("upgrade") ?? "")) {
  return new Response("Expected WebSocket handshake", { status: 400 });
}
const ws = Deno.upgradeWebSocket(request);
Defensive patterns

Strategy: validation

Validate before calling

const up = request.headers.get("upgrade") ?? "";
const conn = request.headers.get("connection") ?? "";
const isWs = up.split(",").some((v) => v.trim().toLowerCase() === "websocket") &&
  conn.split(",").some((v) => v.trim().toLowerCase() === "upgrade") &&
  request.headers.has("sec-websocket-key");
if (!isWs) return new Response("Expected WebSocket", { status: 400 });
const ws = Deno.upgradeWebSocket(request);

Type guard

const isWebSocketHandshake = (req: Request): boolean => {
  const list = (h: string | null) => (h ?? "").split(",").map((s) => s.trim().toLowerCase());
  return list(req.headers.get("upgrade")).includes("websocket");
};

Try / catch

try {
  const { socket, response } = Deno.upgradeWebSocket(request);
  handleSocket(socket);
  return response;
} catch (err) {
  if (err instanceof TypeError) return new Response("Bad WebSocket handshake", { status: 400 });
  throw err;
}

Prevention

When it happens

Trigger: Routing every request into `Deno.upgradeWebSocket` without checking; plain curl/GET requests hitting the WS endpoint; a reverse proxy that strips the `Upgrade` header before the request reaches Deno.

Common situations: One route serving both REST and WS traffic; nginx/HAProxy not configured to forward upgrade headers; health checks probing the WS endpoint; custom clients with hand-rolled handshakes.

Related errors


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

Appendix: source

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

const loadWebSocket = core.createLazyLoader(
  "ext:deno_websocket/01_websocket.js",
);

const _ws = Symbol("[[associated_ws]]");

const websocketCvf = buildCaseInsensitiveCommaValueFinder("websocket");
const upgradeCvf = buildCaseInsensitiveCommaValueFinder("upgrade");

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",
    );

View on GitHub (pinned to 9ad36f7a2c)