denoland/deno · error · NodeTypeError
ERR_HTTP2_INVALID_CONNECTION_HEADERS
ERR_HTTP2_INVALID_CONNECTION_HEADERS
Error message
HTTP/1 Connection specific headers are forbidden: "${key}" What it means
HTTP/2 forbids HTTP/1.1 connection-control headers: connection, upgrade, http2-settings, keep-alive, proxy-connection, transfer-encoding, and te unless its value is exactly 'trailers' (isIllegalConnectionSpecificHeader, util.ts:595). Any of these in a header set throws ERR_HTTP2_INVALID_CONNECTION_HEADERS (util.ts:872) before HPACK serialization.
Source
Thrown at ext/node/polyfills/internal/http2/util.ts:872
}
const flags = ArrayPrototypeIncludes(neverIndex, key)
? kNeverIndexFlag
: kNoHeaderFlags;
if (key[0] === ":") {
const err = assertValuePseudoHeader(key);
if (err !== undefined) {
throw err;
}
value = escapeNgHeaderValueZeroBytes(value);
pseudoHeaders += `${key}\0${value}\0${flags}`;
count++;
return;
}
if (!checkIsHttpToken(key)) {
throw new ERR_INVALID_HTTP_TOKEN("Header name", key);
}
if (isIllegalConnectionSpecificHeader(key, value)) {
throw new ERR_HTTP2_INVALID_CONNECTION_HEADERS(key);
}
if (isArray) {
for (let j = 0; j < value.length; ++j) {
const val = escapeNgHeaderValueZeroBytes(String(value[j]));
headers += `${key}\0${val}\0${flags}`;
}
count += value.length;
return;
}
value = escapeNgHeaderValueZeroBytes(value);
headers += `${key}\0${value}\0${flags}`;
count++;
}
if (ArrayIsArray(arrayOrMap)) {
for (let i = 0; i < arrayOrMap.length; i += 2) {
const key = arrayOrMap[i];
const value = arrayOrMap[i + 1];View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Strip the forbidden headers before the call: delete connection, upgrade, http2-settings, keep-alive, proxy-connection, transfer-encoding (and te unless 'trailers').
- In proxies, translate instead of forwarding: drop hop-by-hop headers per RFC 7230 section 6.1.
- Use the dedicated websocket-over-HTTP/2 (RFC 8441 :protocol) API rather than upgrade headers.
Example fix
// before
session.request({ ':method': 'GET', ':path': '/', connection: 'keep-alive', 'transfer-encoding': 'chunked' });
// after
const HOP = ['connection', 'upgrade', 'http2-settings', 'keep-alive', 'proxy-connection', 'transfer-encoding'];
const clean = Object.fromEntries(Object.entries(h).filter(([k]) => !HOP.includes(k.toLowerCase())));
session.request({ ':method': 'GET', ':path': '/', ...clean }); Defensive patterns
Strategy: validation
Validate before calling
const HOP_BY_HOP = new Set(['connection', 'upgrade', 'http2-settings', 'keep-alive', 'proxy-connection', 'transfer-encoding']);
const clean = Object.fromEntries(
Object.entries(h).filter(([k, v]) =>
!(HOP_BY_HOP.has(k.toLowerCase()) || (k.toLowerCase() === 'te' && v !== 'trailers'))
),
); Type guard
const isForbiddenConnectionHeader = (k: string, v: string) => ['connection', 'upgrade', 'http2-settings', 'keep-alive', 'proxy-connection', 'transfer-encoding'].includes(k.toLowerCase()) || (k.toLowerCase() === 'te' && v !== 'trailers');
Prevention
- Strip hop-by-hop headers at every HTTP/1 -> HTTP/2 boundary
- Use :protocol (RFC 8441) for websockets over h2, not upgrade headers
When it happens
Trigger: Passing headers such as { connection: 'keep-alive' }, { 'transfer-encoding': 'chunked' }, { upgrade: 'websocket' } or { te: 'gzip' } to http2session.request()/stream.respond(); te: 'trailers' is the sole permitted value.
Common situations: Porting HTTP/1.1 client/server code that set Connection/Transfer-Encoding manually; proxies forwarding upstream HTTP/1.1 headers verbatim into an HTTP/2 request; frameworks that blanket-add connection: keep-alive; misconfigured websocket-over-h2 attempts using the HTTP/1 Upgrade header.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- ERR_HTTP2_CONNECT_AUTHORITY
- ERR_INVALID_ARG_VALUE
- ERR_HTTP2_INVALID_PSEUDOHEADER
- ERR_HTTP2_CONNECT_SCHEME
- ERR_HTTP2_CONNECT_PATH
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/4903482159249461.
Report an issue: GitHub.