denoland/deno · error · NodeError

ERR_HTTP_HEADERS_SENT

ERR_HTTP_HEADERS_SENT

Error message

Cannot set headers after they are sent to the client

What it means

OutgoingMessage#setHeader refuses to operate once this._header is set — i.e. after the header block has been serialized for sending. The 'Cannot set headers after they are sent to the client' ERR_HTTP_HEADERS_SENT with context 'set' fires before any name/value validation. _header is populated by the first _send/_storeHeader, which happens on the first write()/end() or explicit head-flush.

Source

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

      if (this.socket) {
        this.socket.destroy(error);
      } else {
        this.once("socket", function socketDestroyOnConnect(socket: any) {
          socket.destroy(error);
        });
      }

      return this;
    },
    writable: true,
    enumerable: true,
    configurable: true,
  },
  setHeader: {
    __proto__: null,
    value: function setHeader(name: string, value: string) {
      if (this._header) {
        throw new ERR_HTTP_HEADERS_SENT("set");
      }
      validateHeaderName(name);
      validateHeaderValue(name, value);

      let headers = this[kOutHeaders];
      if (headers === null) {
        this[kOutHeaders] = headers = ObjectCreate(null);
      }

      name = StringPrototypeToString(name);
      headers[StringPrototypeToLowerCase(name)] = [name, value];
      return this;
    },
    writable: true,
    enumerable: true,
    configurable: true,
  },
  appendHeader: {

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Move all setHeader calls before the first write/end/send
  2. Guard with if (!res.headersSent) { res.setHeader(...) }
  3. For late metadata, use trailers (addTrailers) instead of headers when the protocol allows

Example fix

// before
res.end(body);
res.setHeader('x-trace', traceId); // throws

// after
res.setHeader('x-trace', traceId);
res.end(body);
Defensive patterns

Strategy: validation

Validate before calling

function safeSetHeader(res, name, value) {
  if (!res.headersSent) res.setHeader(name, value);
  else debug('header too late:', name);
}
safeSetHeader(res, 'x-request-id', id);

Type guard

const headersMutable = (msg) => !msg.headersSent && !msg.finished;

Try / catch

try { res.setHeader(name, value); } catch (e) { if (e.code === 'ERR_HTTP_HEADERS_SENT') { /* too late: log and continue */ } else throw e; }

Prevention

When it happens

Trigger: res.setHeader('x-request-id', id) inside a 'finish' listener; error handlers that set headers after the response body already started streaming; calling setHeader after end() was invoked.

Common situations: Express-style middleware that sets headers after a route handler already called res.send(); async work (logging, metrics) completing after the response flushed and then touching headers; retry wrappers that mutate headers per attempt on the same response object.

Related errors


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