{"record":{"id":"c8662196ef2cb991","repo":"denoland/deno","slug":"invalid-header-upgrade-header-must-contain-web","errorCode":null,"errorMessage":"Invalid Header: 'upgrade' header must contain 'websocket'","messagePattern":"Invalid Header: 'upgrade' header must contain 'websocket'","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/http/02_websocket.ts","lineNumber":47,"sourceCode":"const loadWebSocket = core.createLazyLoader(\n  \"ext:deno_websocket/01_websocket.js\",\n);\n\nconst _ws = Symbol(\"[[associated_ws]]\");\n\nconst websocketCvf = buildCaseInsensitiveCommaValueFinder(\"websocket\");\nconst upgradeCvf = buildCaseInsensitiveCommaValueFinder(\"upgrade\");\n\nfunction upgradeWebSocket(request, options = { __proto__: null }) {\n  const inner = toInnerRequest(request);\n  if (inner._wantsUpgrade) {\n    inner._throwIfUpgraded();\n  }\n  const upgrade = request.headers.get(\"upgrade\");\n  const upgradeHasWebSocketOption = upgrade !== null &&\n    websocketCvf(upgrade);\n  if (!upgradeHasWebSocketOption) {\n    throw new TypeError(\n      \"Invalid Header: 'upgrade' header must contain 'websocket'\",\n    );\n  }\n\n  const connection = request.headers.get(\"connection\");\n  const connectionHasUpgradeOption = connection !== null &&\n    upgradeCvf(connection);\n  if (!connectionHasUpgradeOption) {\n    throw new TypeError(\n      \"Invalid Header: 'connection' header must contain 'Upgrade'\",\n    );\n  }\n\n  const websocketKey = request.headers.get(\"sec-websocket-key\");\n  if (websocketKey === null) {\n    throw new TypeError(\n      \"Invalid Header: 'sec-websocket-key' header must be set\",\n    );","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/http/02_websocket.ts#L29-L65","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Gate the call: only upgrade when `request.headers.get('upgrade')` includes 'websocket'","Configure the proxy: `proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"`","Return a normal Response (e.g. 400) for non-WebSocket requests"],"exampleFix":"// before\nconst ws = Deno.upgradeWebSocket(request);\n\n// after\nif (!/websocket/i.test(request.headers.get(\"upgrade\") ?? \"\")) {\n  return new Response(\"Expected WebSocket handshake\", { status: 400 });\n}\nconst ws = Deno.upgradeWebSocket(request);","handlingStrategy":"validation","validationCode":"const up = request.headers.get(\"upgrade\") ?? \"\";\nconst conn = request.headers.get(\"connection\") ?? \"\";\nconst isWs = up.split(\",\").some((v) => v.trim().toLowerCase() === \"websocket\") &&\n  conn.split(\",\").some((v) => v.trim().toLowerCase() === \"upgrade\") &&\n  request.headers.has(\"sec-websocket-key\");\nif (!isWs) return new Response(\"Expected WebSocket\", { status: 400 });\nconst ws = Deno.upgradeWebSocket(request);","typeGuard":"const isWebSocketHandshake = (req: Request): boolean => {\n  const list = (h: string | null) => (h ?? \"\").split(\",\").map((s) => s.trim().toLowerCase());\n  return list(req.headers.get(\"upgrade\")).includes(\"websocket\");\n};","tryCatchPattern":"try {\n  const { socket, response } = Deno.upgradeWebSocket(request);\n  handleSocket(socket);\n  return response;\n} catch (err) {\n  if (err instanceof TypeError) return new Response(\"Bad WebSocket handshake\", { status: 400 });\n  throw err;\n}","preventionTips":["Check the Upgrade header before calling upgradeWebSocket on shared routes","Forward hop-by-hop headers through proxies (Upgrade, Connection, Sec-WebSocket-*)","Expect non-WS clients (health checks, curl) on public endpoints and answer 400"],"tags":["websocket","http","upgrade","proxy"],"backgroundTag":"websocket-upgrade-failed","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}