Node.js ERR_HTTP_HEADERS_SENT — Headers Already Sent
Diagnose and fix ERR_HTTP_HEADERS_SENT in Node.js, raised when one request tries to set response headers a second time after they were already flushed.
Symptoms
- Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
- Express logs the error and the client receives a partial or empty body
- A second res.json() or res.redirect() silently produces no output
Possible causes
- Two response calls (res.send / res.json / res.end / res.redirect) execute for one request
- An async callback or promise resolves and writes to res after the first response finished
- Error-handling middleware calls next(err) and then also tries to send a response
- A redirect is followed by code that continues executing instead of returning
Diagnosis steps
Find every response-writing call in the route and its helpers
grep -n 'res\.\(send\|json\|end\|redirect\|write\)' server.jsInterpretation
More than one response call on a shared path is the usual culprit; note whether any branch is missing a return.
Log res.headersSent at the start of each candidate branch
console.log('sent?', res.headersSent)Interpretation
If the second branch logs sent? true, a response was already flushed and this branch must not write again.
Reproduce with a route that calls a callback after responding
Interpretation
Late promise resolution is the classic trigger: the handler responds, then the awaited callback fires and calls res.send again.
Root cause
A Node HTTP response is a one-shot stream: headers are flushed on the first write and cannot be replaced. ERR_HTTP_HEADERS_SENT means two code paths both believe they own the response — typically a handler that responds, then an async callback or error middleware that responds again.
Fix
Guarantee a single response per request. Return immediately after every res.send/res.json/res.end/res.redirect, abort fall-through logic, and guard late callbacks with `if (res.headersSent) return;`. In Express error middleware, check res.headersSent before sending and delegate to the default handler otherwise.
Verify the fix
Add temporary logging of res.headersSent inside each branch and call the failing endpoint. The error disappears and only one response write is logged per request.
Symptoms
The server throws ERR_HTTP_HEADERS_SENT and the client sees a truncated body or a hang. It often surfaces under load or on a specific branch, not on every request.
Diagnosis
Grep the route for multiple response calls and inspect every branch for a missing return. Then log res.headersSent right before each write:
if (res.headersSent) {
console.warn('skipping second response');
return;
}
A quick reproduction is a handler that responds and then awaits a callback which also calls res.json().
Fix
Make exactly one response win. End every branch with return, and protect asynchronous continuations with the headersSent guard. Error middleware must not respond if an earlier write already happened.
return res.status(500).json({ error: 'boom' });
Verification
Re-run the endpoint with logging enabled. You should see a single response write and no ERR_HTTP_HEADERS_SENT. Load-testing the route confirms the error is gone under concurrency.