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
- 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
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
- 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
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
- Already closed
- Already upgraded
- Invalid Header: 'connection' header must contain 'Upgrade'
- ERR_INVALID_URL
- ERR_PROXY_INVALID_CONFIG
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)