denoland/deno · error · NodeError

ERR_HTTP_TRAILER_INVALID

ERR_HTTP_TRAILER_INVALID

Error message

Trailers are invalid with this transfer encoding

What it means

RFC 7230 permits trailer fields only after a body that uses chunked transfer coding. During header serialization (_storeHeader) the polyfill checks `this.chunkedEncoding !== true && state.trailer` and throws ERR_HTTP_TRAILER_INVALID, because a non-chunked message ends at the first empty line after the header block and has no place to put trailers.

Source

Thrown at ext/node/polyfills/_http_outgoing.ts:1051

          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, value: any) {
      if (ArrayIsArray(value)) {
        // RFC 6265: join multiple Cookie values with '; '

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Remove the Trailer header and the addTrailers() call when the transfer coding is not chunked
  2. Let the message be chunked: keep HTTP/1.1 and do not set Content-Length (call res.removeContentLength() if a framework set it for you)
  3. If you control the client, upgrade it to HTTP/1.1 so chunked encoding is negotiated

Example fix

// before
res.setHeader('Content-Length', 5);
res.setHeader('Trailer', 'X-Sum');
res.end('hello'); // throws ERR_HTTP_TRAILER_INVALID

// after (chunked)
res.removeHeader('Content-Length');
res.setHeader('Trailer', 'X-Sum');
res.write('hello');
res.addTrailers({ 'X-Sum': '5' });
res.end();
Defensive patterns

Strategy: validation

Validate before calling

// only promise trailers when the message is actually chunked
const canTrailer = res.chunkedEncoding === true;
if (canTrailer) res.setHeader('Trailer', 'X-Sum');
// ... later
if (canTrailer) res.addTrailers({ 'X-Sum': String(sum) });

Try / catch

try {
  res.setHeader('Trailer', 'X-Metadata');
  res.end(body);
} catch (e) {
  if (e?.code === 'ERR_HTTP_TRAILER_INVALID') {
    res.removeHeader('Trailer');
    res.end(body); // retry without trailers
  } else throw e;
}

Prevention

When it happens

Trigger: Setting `res.setHeader('Trailer', 'X-Metadata')` or calling res.addTrailers() on a message that will not be chunked: serving an HTTP/1.0 client (no chunked support), a response with an explicit Content-Length (identity coding), or a message where chunkedEncoding was disabled.

Common situations: Testing trailer support against an HTTP/1.0 client or a server/framework that always sets Content-Length; gRPC-style metadata patterns applied to plain non-chunked responses; enabling trailers without removing Content-Length.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/744304aa481a09dc. Report an issue: GitHub.