{"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":"validation","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/89f33cbef296a2b287f323d42de54c871fa69c77/ext/http/02_websocket.ts#L29-L65","documentation":"Deno.upgradeWebSocket(request) validates the WebSocket handshake the client sent. The request's 'upgrade' header must contain the token 'websocket' (case-insensitive, comma-separated list, matched by websocketCvf). A missing header, or values like 'h2c', 'websocketx', or a typo, fail this check.","triggerScenarios":"Calling Deno.upgradeWebSocket(req) inside a plain HTTP GET handler (no upgrade requested); client sent Upgrade: h2c or a custom protocol; a proxy (nginx, ALB) stripped or rewrote the Upgrade header; testing the route with curl without the upgrade headers.","commonSituations":"Reverse proxies not forwarding Upgrade/Connection headers (proxy_setupgrade upgrade missing); load balancers terminating WebSocket upgrades; development probes (curl/health checks) hitting the ws route; shared handlers that run for both ws and non-ws requests.","solutions":["Only call upgradeWebSocket when the request is a real handshake: check req.headers.get('upgrade')?.toLowerCase().includes('websocket') first.","Fix the proxy: nginx needs proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\".","Connect with a real WebSocket client (new WebSocket('ws://...') or ws:// in browser) instead of plain HTTP.","Route WebSocket handshakes to a dedicated handler so ordinary requests never reach the upgrade code."],"exampleFix":"// before\nfunction handler(req: Request) {\n  const { socket, response } = Deno.upgradeWebSocket(req); // crashes for plain GET\n  ...\n}\n\n// after\nfunction handler(req: Request) {\n  if (req.headers.get(\"upgrade\")?.toLowerCase() !== \"websocket\") {\n    return new Response(\"expected websocket\", { status: 400 });\n  }\n  const { socket, response } = Deno.upgradeWebSocket(req);\n  return response;\n}","handlingStrategy":"validation","validationCode":"function isWebSocketHandshake(req: Request): boolean {\n  const up = (req.headers.get(\"upgrade\") ?? \"\").toLowerCase();\n  return up.split(\",\").map((s) => s.trim()).includes(\"websocket\");\n}","typeGuard":"function isWsUpgradeRequest(req: Request): boolean {\n  const tok = (h: string | null) => (h ?? \"\").toLowerCase().split(\",\").map((s) => s.trim());\n  return tok(req.headers.get(\"upgrade\")).includes(\"websocket\") &&\n    tok(req.headers.get(\"connection\")).includes(\"upgrade\") &&\n    req.headers.has(\"sec-websocket-key\");\n}","tryCatchPattern":"try { return Deno.upgradeWebSocket(req).response; } catch (e) { if (e instanceof TypeError && e.message.includes(\"'upgrade' header\")) { return new Response(\"websocket handshake required\", { status: 400 }); } throw e; }","preventionTips":["Route ws handshakes to a dedicated handler so plain HTTP never reaches upgrade code.","Verify proxy config forwards Upgrade/Connection headers.","Health-check the ws route with method/path checks, not real upgrades."],"tags":["websocket","http-headers","upgrade","proxy"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}