# Error [ERR_HTTP_HEADERS_SENT]: “Cannot set headers after they are sent to the client”

> ERR_HTTP_HEADERS_SENT in Express or Node.js. Return after res.send, stop calling next() after a response, and check headersSent in error handlers.

Canonical: https://app369.com/fix/cannot-set-headers-after-they-are-sent/
Last updated: 2026-11-08

The server logs Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client, with a stack trace into res.setHeader, res.json or res.redirect. The first response already went out, and the same request handler then tried to send a second one.

**Short answer**

Find the second response in the stack trace and make every response the last thing that path does: write return res.status(400).json(...) instead of res.status(400).json(...) followed by more code. Don’t call next() after sending a response. In callbacks, loops and try/catch blocks, check that only one branch can send. In a custom error handler, check res.headersSent and pass the error to next(err) if a response has already started.

## **Why a second response is sent, and the fix**

1. 01 ### **Missing return after sending** res.json(), res.send() and res.redirect() send the response but do not stop the function. Code after them runs and sends again. Return the call.```
app.post('/signup', async (req, res) => {
  if (!req.body.email) {
    return res.status(400).json({ error: 'Email is required' });
  }
  const user = await createUser(req.body);
  res.status(201).json(user);
});
```
2. 02 ### **next() called after a response** Middleware that sends a response and then calls next() lets the next route send its own. Middleware should do one or the other.```
// bug: res.status(401).end(); next();
function requireUser(req, res, next) {
  if (!req.user) return res.status(401).end();
  next();
}
```
3. 03 ### **A response inside a loop, callback or both try and catch** res.send() inside forEach, or in a callback that can fire twice, or in try with another one after the catch, sends more than once. Collect the result first, then send one response at the end.```
app.get('/report', async (req, res, next) => {
  try {
    const rows = await db.query(sql);
    return res.json(rows);
  } catch (err) {
    return next(err);
  }
});
```
4. 04 ### **An error handler that responds after streaming began**
  > “you must delegate to the default Express error handler, when the headers have already been sent to the client” [**Express, Error handling: the default error handler**](https://expressjs.com/en/guide/error-handling.html) If an error happens while a response is already streaming, a custom error handler cannot send a new status. Check res.headersSent and hand the error to Express’s default handler.```
app.use((err, req, res, next) => {
  if (res.headersSent) return next(err);
  res.status(500).json({ error: 'Something went wrong' });
});
```

Checked against the vendor’s docs on November 8, 2026: [Express: Error handling](https://expressjs.com/en/guide/error-handling.html).

## **Rather hand it over?**

Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.

- [from $450**App Fix Ticket**One reproducible bug in an existing mobile or web app, diagnosed and fixed.**See scope and start**](https://app369.com/fixed-price/flutter-firebase-bug-fix/)
- [from $450**AI-Built App Rescue**A Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.****No issues worth fixing, no charge.**** If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.**See scope and start**](https://app369.com/fixed-price/ai-app-rescue/)

Related: [**Express “Cannot GET /”**](https://app369.com/fix/express-cannot-get/) · [**“Uncaught (in promise)” errors**](https://app369.com/fix/uncaught-in-promise/) · [**Node.js ECONNRESET (“socket hang up”)**](https://app369.com/fix/node-econnreset/)

Questions

## **More on this error**

<details>

<summary>**Does the client get the first or the second response? **+</summary>

The first. The second call throws on the server, so the client sees the first response, while your logs fill with errors and later code in that handler may not run. In an Express 4 async handler the error can also crash the process, so catch it and call next(err), or use Express 5, which forwards rejected promises.

</details>

<details>

<summary>**Why does it only happen sometimes? **+</summary>

Usually because two branches can both send only for certain input, such as a validation error followed by the normal response, or a callback that fires twice on retries.

</details>

<details>

<summary>**Can you fix it for me? **+</summary>

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.

</details>

A different error?

## **Describe it and get a written quote**

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; Simon replies by email, usually within one business day.

[**Describe your problem **→](https://app369.com/contact/) [**All fixes**](https://app369.com/fix/)