denoland/deno · error · Error
ERR_HTTP2_STATUS_101
ERR_HTTP2_STATUS_101
Error message
HTTP status code 101 (Switching Protocols) is forbidden in HTTP/2
What it means
HTTP/2 removed the HTTP/1.1 Upgrade mechanism, so status 101 (Switching Protocols) is explicitly forbidden on an HTTP/2 stream. additionalHeaders() throws ERR_HTTP2_STATUS_101 when the informational header block carries :status 101 — WebSockets-over-Upgrade cannot work this way in h2.
Source
Thrown at ext/node/polyfills/http2.ts:3460
// a 1xx informational code and it MUST be sent before the request/response
// headers are sent, or an error will be thrown.
additionalHeaders(headers) {
if (this.destroyed || this.closed) {
throw new ERR_HTTP2_INVALID_STREAM();
}
if (this.headersSent) {
throw new ERR_HTTP2_HEADERS_AFTER_RESPOND();
}
assertIsObject(headers, "headers");
headers = ObjectAssign({ __proto__: null }, headers);
debugStreamObj(this, "sending additional headers");
if (headers[HTTP2_HEADER_STATUS] != null) {
const statusCode = headers[HTTP2_HEADER_STATUS] |= 0;
if (statusCode === HTTP_STATUS_SWITCHING_PROTOCOLS) {
throw new ERR_HTTP2_STATUS_101();
}
if (statusCode < 100 || statusCode >= 200) {
throw new ERR_HTTP2_INVALID_INFO_STATUS(headers[HTTP2_HEADER_STATUS]);
}
}
this[kUpdateTimer]();
const headersList = buildNgHeaderString(
headers,
assertValidPseudoHeaderResponse,
this.session[kStrictSingleValueFields],
);
if (!this[kInfoHeaders]) {
this[kInfoHeaders] = [headers];
} else {
ArrayPrototypePush(this[kInfoHeaders], headers);
}View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Use the RFC 8441 extended CONNECT (:method CONNECT + :protocol websocket) instead of 101
- Serve WebSocket traffic on a separate HTTP/1.1 listener or h2 prior-knowledge-free endpoint
- If you control the protocol, answer with a normal 200 and switch at the application layer
- Strip :status 101 before relaying upstream headers into additionalHeaders()
Example fix
// before
stream.additionalHeaders({ ":status": 101, upgrade: "websocket" });
// after
// RFC 8441: no 101 in h2; use extended CONNECT on the session
// const stream = session.request({ ":method": "CONNECT", ":path": "/chat", ":protocol": "websocket" }); Defensive patterns
Strategy: validation
Validate before calling
const status = headers[":status"];
if (status === 101) {
throw new Error("101 Switching Protocols is impossible over HTTP/2; use RFC 8441 CONNECT or a separate h1 endpoint");
} Type guard
function isH2SafeInfoStatus(status) {
const n = Number(status);
return Number.isInteger(n) && n >= 100 && n < 200 && n !== 101;
} Try / catch
try {
stream.additionalHeaders(headers);
} catch (err) {
if (err.code === "ERR_HTTP2_STATUS_101") {
// fall back: negotiate WebSocket outside h2 or reject the upgrade
stream.respond({ ":status": 501 }, { endStream: true });
return;
}
throw err;
} Prevention
- Do not port HTTP/1.1 Upgrade middleware onto an http2 server
- Bootstrap WebSockets over h2 with extended CONNECT (:protocol), per RFC 8441
When it happens
Trigger: Passing { ':status': 101 } (or any headers object whose :status coerces to 101) to additionalHeaders(); porting HTTP/1.1 WebSocket-upgrade middleware onto an http2 server; forwarding 101 from an upstream.
Common situations: Running WebSocket code on an h2-only server (RFC 8441 bootstraps WebSockets differently, via CONNECT with :protocol); proxies translating h1 upgrade responses into h2.
Understand the failure class
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- ERR_HTTP2_INVALID_INFO_STATUS
- ERR_HTTP2_STATUS_INVALID
- ERR_HTTP2_HEADERS_SENT
- ERR_INVALID_ARG_TYPE
- ERR_HTTP2_PAYLOAD_FORBIDDEN
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/f5a07253bb7aa0d2.
Report an issue: GitHub.