This error occurs when Node.js detects conflicting or invalid HTTP trailer headers in a chunked transfer encoding response. Trailers can only be used with chunked encoding, but certain conditions prevent their proper use or declaration.
The ERR_HTTP_INVALID_TRAILER error is raised by Node.js when there is a configuration problem with HTTP trailer headers in relation to chunked transfer encoding. Trailers are HTTP headers that are sent after the message body in chunked encoding, indicated by the Trailer header field which must list all trailer names beforehand. This error typically indicates one of these conditions: (1) attempting to use trailers without chunked encoding enabled, (2) trailer headers that weren't declared in the Trailer header field, (3) invalid trailer header names or values, or (4) improper trailer setup in the response context. HTTP trailers allow servers to send metadata about the response body after transmission (useful for checksums, digital signatures, or post-processing information), but they require specific setup and adherence to HTTP/1.1 specifications.
Use the Trailer header field to declare which trailer headers you intend to send after the response body. This must be set before any body data is written:
const http = require('http');
const server = http.createServer((req, res) => {
// Declare which trailers will be sent after the body
res.setHeader('Trailer', 'X-Checksum, X-Processing-Time');
// Enable chunked encoding
res.setHeader('Transfer-Encoding', 'chunked');
res.setHeader('Content-Type', 'application/json');
// Send the body
res.write(JSON.stringify({ data: 'some data' }));
res.write(JSON.stringify({ more: 'data' }));
// Add trailers at the end, before calling end()
res.addTrailers({
'X-Checksum': 'abc123def456',
'X-Processing-Time': '150ms'
});
res.end();
});
server.listen(3000);The Trailer header must list all trailer names that will be sent after the body.
Trailers can only be used with chunked transfer encoding. Explicitly set Transfer-Encoding: chunked if you plan to use trailers:
const http = require('http');
const server = http.createServer((req, res) => {
// REQUIRED: Set chunked encoding for trailers to work
res.setHeader('Transfer-Encoding', 'chunked');
// Declare trailers
res.setHeader('Trailer', 'X-Signature');
res.write('First chunk of data\n');
res.write('Second chunk of data\n');
// Add trailers before ending
res.addTrailers({
'X-Signature': 'signature-value'
});
res.end();
});
server.listen(3000);Do NOT use Content-Length header when using trailers, as it conflicts with chunked encoding.
Trailer header names must be valid HTTP header field names (alphanumeric with hyphens). Some headers like Content-Length, Transfer-Encoding, and Host cannot be trailers:
const http = require('http');
const server = http.createServer((req, res) => {
// Valid trailer names (custom or standard metadata headers)
res.setHeader('Trailer', 'X-File-Checksum, X-Completion-Status');
res.setHeader('Transfer-Encoding', 'chunked');
res.write('Data chunk 1\n');
res.write('Data chunk 2\n');
// Valid trailers
res.addTrailers({
'X-File-Checksum': 'sha256:abc123',
'X-Completion-Status': 'success'
});
res.end();
});
server.listen(3000);Avoid these forbidden trailer names: Content-Length, Transfer-Encoding, Host, Cache-Control, Expect, Max-Forwards, Pragma, Range, TE, Trailer, Authorization, Proxy-Authorization, etc.
Trailers must be added using addTrailers() after the body has been written but before calling res.end(). Do not try to set them as response headers:
const http = require('http');
const server = http.createServer((req, res) => {
// Set trailers metadata
res.setHeader('Trailer', 'X-Processing-Time, X-Records-Count');
res.setHeader('Transfer-Encoding', 'chunked');
res.setHeader('Content-Type', 'application/json');
const data = [
{ id: 1, name: 'Record 1' },
{ id: 2, name: 'Record 2' }
];
// Send body
data.forEach(record => {
res.write(JSON.stringify(record) + '\n');
});
// Add trailers BEFORE res.end()
res.addTrailers({
'X-Processing-Time': '42ms',
'X-Records-Count': data.length.toString()
});
res.end();
});
server.listen(3000);Calling addTrailers() after res.end() will throw an error.
Trailers are only supported in HTTP/1.1 and HTTP/2. For HTTP/1.0 clients, trailers will be ignored or cause errors:
const http = require('http');
const server = http.createServer((req, res) => {
// Check HTTP version
console.log('HTTP version:', req.httpVersion);
if (req.httpVersion < '1.1') {
// HTTP/1.0: Don't use trailers
res.setHeader('Content-Type', 'application/json');
res.write(JSON.stringify({ message: 'HTTP/1.0 detected' }));
res.end();
return;
}
// HTTP/1.1 or higher: Safe to use trailers
res.setHeader('Trailer', 'X-Version-Info');
res.setHeader('Transfer-Encoding', 'chunked');
res.write('Data for HTTP/1.1+');
res.addTrailers({
'X-Version-Info': 'HTTP/' + req.httpVersion
});
res.end();
});
server.listen(3000);When consuming responses with trailers, listen for the trailers event to access them:
const http = require('http');
const options = {
hostname: 'localhost',
port: 3000,
path: '/',
method: 'GET'
};
const req = http.request(options, (res) => {
console.log('Status:', res.statusCode);
res.on('data', (chunk) => {
console.log('Body chunk:', chunk.toString());
});
// Listen for trailers
res.on('trailers', (trailers) => {
console.log('Trailers received:', trailers);
console.log('Checksum:', trailers['x-checksum']);
console.log('Processing time:', trailers['x-processing-time']);
});
res.on('end', () => {
console.log('Response complete');
});
});
req.on('error', (e) => {
console.error('Request error:', e);
});
req.end();The trailers event is emitted when all trailers have been received, after the body data.
HTTP/2 Trailers: HTTP/2 has different trailer semantics than HTTP/1.1. In HTTP/2, trailers are sent as header frames after the data frames, and they don't require explicit Transfer-Encoding declaration since HTTP/2 uses frame-based framing. The mechanics are similar but handled differently by the protocol.
Compression and Trailers: When using gzip or deflate compression with trailers, ensure compression is applied only to the body, not the trailers. Some middleware may inadvertently compress trailers or conflict with trailer transmission. Use stream.pipeline() for better composition.
Proxy Compatibility: Many proxies and intermediaries strip or don't support HTTP trailers due to legacy HTTP/1.0 compatibility. In production, trailers may not reach the client if routing through incompatible proxies. Test in your specific network environment.
Performance Considerations: Trailers are useful for checksums (MD5, SHA-256) or digital signatures computed while streaming, avoiding the need to compute them upfront or buffer the entire response. This is more efficient for large responses. However, clients must support and expect trailers, so always document their use.
WebSocket and Upgrades: Trailer headers cannot be used with WebSocket upgrades or other protocol upgrade scenarios. The Upgrade header and trailers conflict, as WebSocket uses a different framing mechanism.
Forbidden Trailer Names: According to RFC 7230, certain header fields are never allowed as trailers: Authorization, Content-Encoding, Content-Length, Content-Range, Host, Pragma, Proxy-Authenticate, Proxy-Authorization, Range, TE, Trailer, Transfer-Encoding, WWW-Authenticate, and Cache-Control.
Debugging Tips: Use curl with verbose mode (curl -v) to see trailers in responses. Add logging at the point where addTrailers() is called to ensure it's executed before res.end(). Check HTTP version in server logs to verify clients support trailers.
Express.js Compatibility: Express doesn't have built-in middleware for trailers. You'll need to work directly with the response object or create custom middleware that properly handles trailer headers and events.
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams