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_SENTappears (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.
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();
});
});
});Client: HTTP 200 "First"
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the clientThe 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.
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();
});
});
});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.
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();
});
});
});Client: HTTP 200 "Sending response before data is fetched"
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the clientThe 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).
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();
});
});
});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.
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();
});
});
});Client: HTTP 200 "Middleware response"
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the clientThe 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.
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();
});
});
});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.
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();
});
});
});Client: HTTP 200 "Initial response"
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the clientThe 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.
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();
});
});
});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".
5. Related — removeHeader after the response (cannot remove headers after they are sent)
The same committed-response state blocks removing headers:
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());
});
});ERR_HTTP_HEADERS_SENTThe 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.endshould run per request; usereturnto stop falling through. - In async handlers, avoid sending before
awaitresolves unless that is intentional. - After any send, skip
next()in middleware that ends the response; in error handlers, checkres.headersSentbefore sending again.
References
Express, Node, and MDN background for HTTP responses and async handlers.
- Express.js documentation
- Node.js HTTP
ServerResponse - MDN: HTTP headers
- MDN: HTTP response status codes
- MDN:
async function

