denoland/deno · error · Error
ERR_HTTP_TRAILER_INVALID
ERR_HTTP_TRAILER_INVALID
Error message
Trailers are invalid with this transfer encoding
What it means
OutgoingMessage._storeHeader rejects a message that declares a Trailer header while its transfer encoding is not chunked (this.chunkedEncoding !== true). HTTP trailers are only defined for chunked bodies - a non-chunked message ends at the first empty line after headers, so a trailer could never be delivered - hence ERR_HTTP_TRAILER_INVALID at header-assembly time.
Solutions
- Remove the explicit content-length header so an HTTP/1.1 response can use chunked encoding with trailers.
- Drop the Trailer header and send the metadata as a normal header or in the body when you must keep content-length.
- Ensure the client connection is HTTP/1.1 (chunked is not negotiated for 1.0 responses).
Example fix
// before
res.setHeader('Trailer', 'X-Checksum');
res.setHeader('content-length', body.length); // chunking off -> throws
res.end(body);
// after
res.setHeader('Trailer', 'X-Checksum');
res.removeHeader('content-length'); // chunked encoding -> trailers legal
res.write(body);
res.addTrailers({ 'X-Checksum': sum });
res.end(); Defensive patterns
Strategy: validation
Validate before calling
// trailers are only legal with chunked encoding: no content-length and HTTP/1.1
const canTrailer = res.req?.httpVersionMajor === 1 &&
res.req?.httpVersionMinor >= 1 &&
!res.hasHeader('content-length');
if (canTrailer) res.setHeader('Trailer', 'X-Checksum'); Try / catch
try {
res.setHeader('Trailer', 'X-Foo');
res.writeHead(200);
} catch (err) {
if (err.code === 'ERR_HTTP_TRAILER_INVALID') {
res.removeHeader('Trailer');
res.writeHead(200); // retry without trailers
} else throw err;
} Prevention
- Never combine a Trailer header with an explicit content-length.
- Confirm the client is HTTP/1.1 before advertising trailers.
- Send trailing metadata in the body when a fixed content-length is required.
When it happens
Trigger: res.setHeader('Trailer', 'X-Foo') combined with anything that disables chunking: an explicit content-length header, HTTP/1.0 client (server forces _last and no chunked), or writeHead with content-length. Also adding 'Trailer' to an array/object headers argument on a non-chunked request.
Common situations: Adding trailing metadata (checksums, timing headers) without realizing the response got a fixed content-length (e.g. serving a file with known size); proxying HTTP/1.0 clients; frameworks that auto-set content-length for small bodies, silently disabling chunked encoding.
Related errors
- ERR_INVALID_CHAR
- ERR_INVALID_HTTP_TOKEN
- ERR_HTTP_BODY_NOT_ALLOWED
- ERR_HTTP_CONTENT_LENGTH_MISMATCH
- ERR_HTTP_HEADERS_SENT
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/744304aa481a09dc.
Report an issue: GitHub.
Appendix: source
Thrown at ext/node/polyfills/_http_outgoing.ts:1061
this.chunkedEncoding = true;
} else {
// We should only be able to get here if both Content-Length and
// Transfer-Encoding are removed by the user.
// See: test/parallel/test-http-remove-header-stays-removed.js
debug("Both Content-Length and Transfer-Encoding are removed");
// We can't keep alive in this case, because with no header info the body
// is defined as all data until the connection is closed.
this._last = true;
}
}
// Test non-chunked message does not have trailer header set,
// message will be terminated by the first empty line after the
// header fields, regardless of the header fields present in the
// message, and thus cannot contain a message body or 'trailers'.
if (this.chunkedEncoding !== true && state.trailer) {
throw new ERR_HTTP_TRAILER_INVALID();
}
const { header } = state;
this._header = header + "\r\n";
// Wait until the first body chunk, or close(), is sent to flush,
// UNLESS we're sending Expect: 100-continue.
if (state.expect) this._send("");
},
writable: true,
enumerable: true,
configurable: true,
},
_storeHeaderEntry: {
__proto__: null,
value: function _storeHeaderEntry(
state: any,
field: string,View on GitHub (pinned to 9ad36f7a2c)