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) 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.
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 89f33cbef2)
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.
Example fix
// before
function handler(req: Request) {
const { socket, response } = Deno.upgradeWebSocket(req); // crashes for plain GET
...
}
// after
function handler(req: Request) {
if (req.headers.get("upgrade")?.toLowerCase() !== "websocket") {
return new Response("expected websocket", { status: 400 });
}
const { socket, response } = Deno.upgradeWebSocket(req);
return response;
} Defensive patterns
Strategy: validation
Validate before calling
function isWebSocketHandshake(req: Request): boolean {
const up = (req.headers.get("upgrade") ?? "").toLowerCase();
return up.split(",").map((s) => s.trim()).includes("websocket");
} Type guard
function isWsUpgradeRequest(req: Request): boolean {
const tok = (h: string | null) => (h ?? "").toLowerCase().split(",").map((s) => s.trim());
return tok(req.headers.get("upgrade")).includes("websocket") &&
tok(req.headers.get("connection")).includes("upgrade") &&
req.headers.has("sec-websocket-key");
} Try / catch
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; } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid Header: 'connection' header must contain 'Upgrade'
- Already upgraded
- Invalid Header: 'sec-websocket-key' header must be set
- Protocol '${options.protocol}' not in the request's protocol
- Socket is already destroyed - cannot upgrade to WebSocket
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/c8662196ef2cb991.
Report an issue: GitHub.