← Debugging Guides

阅读中文版本

Express — Cannot set headers after they are sent

Fix "headers already sent" by ensuring each request sends exactly one response and guarding asynchronous paths.

intermediatedebuggingnodeexpress

Symptoms

  • Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
  • The client gets a 200 but the server logs an error
  • A response is sent twice in different code branches
  • An async handler sends after awaiting past a timeout

Possible causes

  • A handler sends a response and then another branch also calls send
  • A timer or callback fires after the response already completed
  • Middleware next is called after responding
  • A try/catch sends an error response after success was sent

Diagnosis steps

Find all response calls in the handler

grep -n "res.send\|res.json\|res.end\|res.status" routes/

Interpretation

Listing every response call reveals branches that can each run, doubling the response.

Add a sent guard

if (res.headersSent) return;

Interpretation

Checking headersSent before responding prevents a second send from crashing the process.

Clear timers on response close

res.on('close', () => clearTimeout(timer))

Interpretation

Timers that outlive the response need clearing, or they attempt to write to a finished response.

Root cause

The code attempted to send a second response for a request that had already sent one, which the HTTP layer rejects because headers and body are already committed.

Fix

Ensure each code path sends exactly one response, add a headersSent guard, and stop asynchronously started work when the response closes.

Verify the fix

Reproduce the request and confirm no ERR_HTTP_HEADERS_SENT appears, and confirm the client receives a single well-formed response.

Symptom walkthrough

Express logs ERR_HTTP_HEADERS_SENT while the client still receives what looks like a successful response. The cause is two response attempts for one request: a handler sends and an error path also sends, or a timer or pending callback fires after the response completed. Because headers are already written, the second attempt throws and may crash the process.

Fix in practice

Restructure the handler so each path sends exactly one response, and guard with if (res.headersSent) return before any potential second send. Cancel timers and asynchronous work when the response closes so nothing writes after completion. Return after sending rather than falling through.

Verification

Reproduce the request and confirm the server no longer logs the error and the client receives one response. A single clean response with no server error proves the paths no longer double-send.

Diagnostic commands

Check the behavior with commands like these:

grep -n "res.send\|res.json\|res.end\|res.status" routes/
if (res.headersSent) return;
res.on('close', () => clearTimeout(timer))