denoland/deno · error · TypeError
Invalid Header: 'sec-websocket-key' header must be set
Error message
Invalid Header: 'sec-websocket-key' header must be set
What it means
RFC 6455 requires the client to send a random `Sec-WebSocket-Key`; the server hashes it to produce `Sec-WebSocket-Accept`. `Deno.upgradeWebSocket` reads it via `request.headers.get('sec-websocket-key')` and throws this TypeError when it is absent, because the accept header — and therefore a valid 101 response — cannot be computed.
Solutions
- Use a spec-compliant WebSocket client (browser WebSocket, or a ws library)
- Fix the proxy/WAF to forward all `Sec-WebSocket-*` headers
- Validate the handshake headers up front and answer 400 for incomplete ones
Example fix
// before
const ws = Deno.upgradeWebSocket(request);
// after
if (request.headers.get("sec-websocket-key") === null) {
return new Response("Missing Sec-WebSocket-Key", { status: 400 });
}
const ws = Deno.upgradeWebSocket(request); Defensive patterns
Strategy: validation
Validate before calling
if (request.headers.get("sec-websocket-key") === null) {
return new Response("Missing Sec-WebSocket-Key", { status: 400 });
}
const ws = Deno.upgradeWebSocket(request); Type guard
const hasWebSocketKey = (req: Request): boolean =>
req.headers.has("sec-websocket-key"); Try / catch
try {
return Deno.upgradeWebSocket(request);
} catch (err) {
if (err instanceof TypeError && err.message.includes("sec-websocket-key")) {
return new Response("Incomplete WebSocket handshake", { status: 400 });
}
throw err;
} Prevention
- Use spec-compliant WebSocket clients for integration tests
- Audit WAF/proxy rules so Sec-WebSocket-* headers survive the hop
- Reject incomplete handshakes with 400 before they reach upgrade code
When it happens
Trigger: Non-browser clients (raw TCP scripts, custom embedded clients, curl) that send Upgrade/Connection headers but no key; intermediaries stripping `Sec-*` headers; replayed or crafted requests.
Common situations: Load balancers or WAFs dropping unrecognized `Sec-WebSocket-*` headers; IoT/embedded clients with incomplete handshake implementations; test harnesses issuing partial handshakes.
Related errors
- Invalid Header: 'connection' header must contain 'Upgrade'
- Already closed
- Already upgraded
- ERR_HTTP_HEADERS_SENT
- ERR_HTTP_INVALID_HEADER_VALUE
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/934a66b8449aaeba.
Report an issue: GitHub.
Appendix: source
Thrown at ext/http/02_websocket.ts:63
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",
);
}
const accept = op_http_websocket_accept_header(websocketKey);
const r = newInnerResponse(101);
r.headerList = [
["upgrade", "websocket"],
["connection", "Upgrade"],
["sec-websocket-accept", accept],
];
const protocolsStr = request.headers.get("sec-websocket-protocol") || "";
const protocols = StringPrototypeSplit(protocolsStr, ", ");
if (protocols && options.protocol) {
if (ArrayPrototypeIncludes(protocols, options.protocol)) {
ArrayPrototypePush(r.headerList, [View on GitHub (pinned to 9ad36f7a2c)