Cannot set headers after they are sent to the client (Express & Node.js)

Fix Error [ERR_HTTP_HEADERS_SENT] and cannot set headers after they are sent in Express/Node: double res.send, async races, middleware next(), error handlers, and res.headersSent—with tested client and log lines.

Published

Updated

Read time 8 min read

Reviewed byDeepak Prasad

Cannot set headers after they are sent to the client (Express & Node.js)

If you see Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client, your server tried to change headers or send another body after the response for that request had already started. That breaks the HTTP rule: one response per request. The same underlying issue appears when tools report server cannot set status after http headers have been sent or cannot remove headers after they are sent to the client.

Each example below was written for Node.js v20.18.2 with express@4.21.1 (npm install express@4.21.1). A tiny in-script http.get issues one request. The public Run sandbox typically does not install Express, so snippets use {run=false}—copy them into your project or read the short description after each block for what the client and stderr show locally.

Tested on: Node.js v20.18.2 and express@4.21.1. Descriptions below summarize the client log line and whether ERR_HTTP_HEADERS_SENT appears (stack depth may differ).


Quick reference

Use this table when debugging cannot set headers after they are sent to the client express or err_http_headers_sent in middleware.

Symptom What to audit
ERR_HTTP_HEADERS_SENT right after res.send / json Second res.* on the same path, missing return, or next() after send
After async work Ensure no early res.send before await completes
In catch / error middleware Wrap error responses with if (!res.headersSent)
cannot write headers / cannot remove headers Same rule—response already started

1. Multiple responses (res.send twice)

Calling res.send (or res.json, res.end, redirect, …) twice in the same handler is the simplest cannot set headers after they are sent to the client express bug.

javascript
const express = require("express");
const http = require("http");

const app = express();
app.get("/t", (req, res) => {
  res.send("First");
  res.send("Second");
});

const server = app.listen(0, () => {
  const port = server.address().port;
  http.get({ hostname: "127.0.0.1", port, path: "/t" }, (res) => {
    let body = "";
    res.on("data", (c) => (body += c));
    res.on("end", () => {
      console.log("Client: HTTP " + res.statusCode + " " + JSON.stringify(body));
      server.close();
    });
  });
});
text
Client: HTTP 200 "First"
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

The client receives HTTP 200 with body "First". Node’s stderr then reports Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client from the second res.send.

Fix: send once—use if / else, return res.send(...), or refactor so only one branch calls a terminal responder. Accidentally falling through after a response is a common reason people need return vs throw discipline in handlers.

javascript
const express = require("express");
const http = require("http");

const app = express();
app.get("/t", (req, res) => {
  const ok = true;
  if (ok) return res.send("Condition is true");
  return res.send("Condition is false");
});

const server = app.listen(0, () => {
  const port = server.address().port;
  http.get({ hostname: "127.0.0.1", port, path: "/t" }, (res) => {
    let body = "";
    res.on("data", (c) => (body += c));
    res.on("end", () => {
      console.log("Client: HTTP " + res.statusCode + " " + JSON.stringify(body));
      server.close();
    });
  });
});
text
Client: HTTP 200 "Condition is true"

2. Async race (respond before the Promise finishes)

Here the handler sends immediately, then tries to send again when the Promise resolves—classic express cannot set headers after they are sent to the client with async code.

javascript
const express = require("express");
const http = require("http");

const app = express();
app.get("/t", (req, res) => {
  new Promise((resolve) => setTimeout(() => resolve("Data from database"), 20)).then(
    (data) => res.send(data),
  );
  res.send("Sending response before data is fetched");
});

const server = app.listen(0, () => {
  const port = server.address().port;
  http.get({ hostname: "127.0.0.1", port, path: "/t" }, (res) => {
    let body = "";
    res.on("data", (c) => (body += c));
    res.on("end", () => {
      console.log("Client: HTTP " + res.statusCode + " " + JSON.stringify(body));
      server.close();
    });
  });
});
text
Client: HTTP 200 "Sending response before data is fetched"
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

The client gets the early string (HTTP 200 with "Sending response before data is fetched"). When the Promise resolves, the second send triggers ERR_HTTP_HEADERS_SENT on stderr (exact timing can vary, but the first body wins).

Depending on timing, this pattern can also surface as an unhandled rejection or non-zero exit in short scripts—the client still receives the first body.

Fix: async handler + await, or put res.send only inside the then (and nowhere else on that path).

javascript
const express = require("express");
const http = require("http");

async function fetchFromDatabase() {
  return new Promise((resolve) => setTimeout(() => resolve("Data from database"), 20));
}

const app = express();
app.get("/t", async (req, res) => {
  const data = await fetchFromDatabase();
  res.send(data);
});

const server = app.listen(0, () => {
  const port = server.address().port;
  http.get({ hostname: "127.0.0.1", port, path: "/t" }, (res) => {
    let body = "";
    res.on("data", (c) => (body += c));
    res.on("end", () => {
      console.log("Client: HTTP " + res.statusCode + " " + JSON.stringify(body));
      server.close();
    });
  });
});
text
Client: HTTP 200 "Data from database"

The client should receive HTTP 200 with body "Data from database" and no header error.


3. Middleware calls next() after res.send

Middleware that ends the response must not call next()—otherwise the router still runs and tries a second res.send.

javascript
const express = require("express");
const http = require("http");

const app = express();
app.use((req, res, next) => {
  if (req.path === "/t") {
    res.send("Middleware response");
    next();
  } else {
    next();
  }
});
app.get("/t", (req, res) => {
  res.send("Route response");
});

const server = app.listen(0, () => {
  const port = server.address().port;
  http.get({ hostname: "127.0.0.1", port, path: "/t" }, (res) => {
    let body = "";
    res.on("data", (c) => (body += c));
    res.on("end", () => {
      console.log("Client: HTTP " + res.statusCode + " " + JSON.stringify(body));
      server.close();
    });
  });
});
text
Client: HTTP 200 "Middleware response"
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

The client sees the middleware body (HTTP 200 with "Middleware response"). The route’s second send then causes ERR_HTTP_HEADERS_SENT on stderr.

Fix: return after res.send, or return next() without sending when you only intend to pass through.

javascript
const express = require("express");
const http = require("http");

const app = express();
app.use((req, res, next) => {
  if (req.path === "/t") return next();
  next();
});
app.get("/t", (req, res) => res.send("Route response"));

const server = app.listen(0, () => {
  const port = server.address().port;
  http.get({ hostname: "127.0.0.1", port, path: "/t" }, (res) => {
    let body = "";
    res.on("data", (c) => (body += c));
    res.on("end", () => {
      console.log("Client: HTTP " + res.statusCode + " " + JSON.stringify(body));
      server.close();
    });
  });
});
text
Client: HTTP 200 "Route response"

4. Error handler runs after headers were sent

If a route res.sends and then throws, Express invokes your (err, req, res, next) handler—but res.status(500).send(...) is another response.

javascript
const express = require("express");
const http = require("http");

const app = express();
app.get("/t", (req, res) => {
  res.send("Initial response");
  throw new Error("An error occurred");
});
app.use((err, req, res, next) => {
  res.status(500).send("Internal Server Error");
});

const server = app.listen(0, () => {
  const port = server.address().port;
  http.get({ hostname: "127.0.0.1", port, path: "/t" }, (res) => {
    let body = "";
    res.on("data", (c) => (body += c));
    res.on("end", () => {
      console.log("Client: HTTP " + res.statusCode + " " + JSON.stringify(body));
      server.close();
    });
  });
});
text
Client: HTTP 200 "Initial response"
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

The client still reads HTTP 200 with "Initial response". Express then hits ERR_HTTP_HEADERS_SENT when the error middleware tries to send 500 after headers were committed.

Fix: guard with res.headersSent and delegate to logging / next(err) when you cannot respond.

javascript
const express = require("express");
const http = require("http");

const app = express();
app.get("/t", (req, res) => {
  res.send("Initial response");
  throw new Error("An error occurred");
});
app.use((err, req, res, next) => {
  if (res.headersSent) {
    return next(err);
  }
  res.status(500).send("Internal Server Error");
});
app.use((err, req, res, next) => {
  console.log("logged:", err.message, "headersSent=", res.headersSent);
});

const server = app.listen(0, () => {
  const port = server.address().port;
  http.get({ hostname: "127.0.0.1", port, path: "/t" }, (res) => {
    let body = "";
    res.on("data", (c) => (body += c));
    res.on("end", () => {
      console.log("Client: HTTP " + res.statusCode + " " + JSON.stringify(body));
      server.close();
    });
  });
});
text
logged: An error occurred headersSent= true
Client: HTTP 200 "Initial response"

Stderr should log something like logged: An error occurred headersSent= true from the fallback handler, while the HTTP client still reports HTTP 200 with "Initial response".


The same committed-response state blocks removing headers:

javascript
const express = require("express");
const http = require("http");

const app = express();
app.get("/t", (req, res) => {
  res.send("ok");
  try {
    res.removeHeader("content-type");
  } catch (e) {
    console.log(e.code);
  }
});

const server = app.listen(0, () => {
  const port = server.address().port;
  http.get({ hostname: "127.0.0.1", port, path: "/t" }, (res) => {
    res.resume();
    res.on("end", () => server.close());
  });
});
text
ERR_HTTP_HEADERS_SENT

The try/catch around removeHeader logs ERR_HTTP_HEADERS_SENT (the error code string) once the response has already been sent.


Summary

  • Only one terminal res.send / res.json / res.end should run per request; use return to stop falling through.
  • In async handlers, avoid sending before await resolves unless that is intentional.
  • After any send, skip next() in middleware that ends the response; in error handlers, check res.headersSent before sending again.

References

Express, Node, and MDN background for HTTP responses and async handlers.


Frequently Asked Questions

1. What does "Cannot set headers after they are sent to the client" mean?

After an HTTP response has started (headers and often the first body bytes flushed), Node.js forbids changing headers or starting a second response on the same request. Express surfaces this as Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.

2. Why do I see this error with Express async routes?

A common pattern is calling res.send early and then calling res.json or res.send again when a Promise resolves. Only one terminal res.* call should run; use async/await, return after sending, or branch with res.headersSent before sending again.

3. How do I fix cannot set headers after they are sent in middleware?

Either end the request in the middleware with res.send/json/end and do not call next(), or call next() without having sent a body yet. Sending a response and then next() lets later handlers try to respond again and triggers ERR_HTTP_HEADERS_SENT.

4. What is res.headersSent for?

res.headersSent is true once Express/Node has committed headers. Use it in error handlers to avoid sending a second HTML/JSON error page after you already streamed a normal response.

5. Is cannot remove headers after they are sent the same problem?

Yes—the response is already committed. removeHeader, setHeader, status, and a second res.send all conflict with that state and throw ERR_HTTP_HEADERS_SENT or similar.

6. Does this error only happen in Express?

No. It originates from Node http.ServerResponse. Express is just where many apps first hit it because res.send and middleware chains make double responses easy.
Steve Alila

Specializes in web design, WordPress development, and data analysis, with proficiency in Python, JavaScript, and data extraction tools. Additionally, he excels in web API development, AI integration, …