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

  1. Use the RFC 8441 extended CONNECT (:method CONNECT + :protocol websocket) instead of 101
  2. Serve WebSocket traffic on a separate HTTP/1.1 listener or h2 prior-knowledge-free endpoint
  3. If you control the protocol, answer with a normal 200 and switch at the application layer
  4. 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

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

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/f5a07253bb7aa0d2. Report an issue: GitHub.