denoland/deno · error · NodeTypeError
ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED
ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED
Error message
Cannot set HTTP/2 pseudo-headers
What it means
The same assertValidHeader in http2 compat rejects pseudo-headers — names beginning with ':' such as :status, :method, :path, :authority, :scheme — with ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED. In HTTP/2 these fields are protocol metadata carried outside the header block, so the generic header APIs (setHeader, appendHeader, setTrailer/addTrailers) refuse them.
Source
Thrown at ext/node/polyfills/internal/http2/compat.js:102
const kAborted = Symbol("aborted");
let statusMessageWarned = false;
let statusConnectionHeaderWarned = false;
// Defines and implements an API compatibility layer on top of the core
// HTTP/2 implementation, intended to provide an interface that is as
// close as possible to the current require('http') API
const assertValidHeader = hideStackFrames((name, value) => {
if (
name === "" ||
typeof name !== "string" ||
StringPrototypeIncludes(name, " ")
) {
throw new ERR_INVALID_HTTP_TOKEN.HideStackFramesError("Header name", name);
}
if (isPseudoHeader(name)) {
throw new ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED.HideStackFramesError();
}
if (value === undefined || value === null) {
throw new ERR_HTTP2_INVALID_HEADER_VALUE.HideStackFramesError(value, name);
}
if (!isConnectionHeaderAllowed(name, value)) {
connectionHeaderMessageWarn();
}
});
function isPseudoHeader(name) {
switch (name) {
case HTTP2_HEADER_STATUS: // :status
case HTTP2_HEADER_METHOD: // :method
case HTTP2_HEADER_PATH: // :path
case HTTP2_HEADER_AUTHORITY: // :authority
case HTTP2_HEADER_SCHEME: // :scheme
return true;
default:View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Use the dedicated APIs: response.statusCode for :status, request.method / request.url for :method/:path
- Strip pseudo-headers before forwarding: omit every key starting with ':'
- When copying h2 request headers, map :authority to host and drop the rest
Example fix
// before
response.setHeader(":status", 201); // ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED
// after
response.statusCode = 201; Defensive patterns
Strategy: validation
Validate before calling
function stripPseudoHeaders(headers) {
return Object.fromEntries(
Object.entries(headers).filter(([k]) => !k.startsWith(":")),
);
}
res.writeHead(200, stripPseudoHeaders(incomingHttp2Headers)); Type guard
function isPseudoHeader(name: string): boolean {
return name.startsWith(":");
} Try / catch
try {
res.setHeader(name, value);
} catch (err) {
if (err.code === "ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED") {
if (name === ":status") res.statusCode = Number(value);
return;
}
throw err;
} Prevention
- Never let ':'-prefixed keys reach setHeader/appendHeader/addTrailers
- Use statusCode/stream APIs for h2 metadata instead of header-shaped syntax
When it happens
Trigger: response.setHeader(":status", 200); response.addTrailers({ ":status": 200 }); copying an incoming h2 request's raw headers object (which contains :method/:path/:authority) directly onto an outgoing response via setHeader loops.
Common situations: Copy-forward proxies replaying h2 header objects; code ported from the h2 client API (h2.request legitimately takes :method/:path) into server response APIs; generic header middleware that serializes metadata as ':'-prefixed keys.
Related errors
- ERR_INVALID_ARG_VALUE
- ERR_INVALID_HTTP_TOKEN
- ERR_HTTP2_INVALID_HEADER_VALUE
- ERR_HTTP2_HEADERS_SENT
- ERR_HTTP2_INVALID_PSEUDOHEADER
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/23a8ec58ccae8024.
Report an issue: GitHub.