Skip to content

Stack traces in error responses

Severity: highApplies to: Express 4.xApplies to: Express 5.xFacts last verified 2026-07-26 against Express 5.2.1 · finalhandler

Express delegates unhandled errors to finalhandler, whose documented behaviour is unambiguous:

The body will be the HTML of the status code message if env is 'production', otherwise will be err.stack.

And from Express’s own lib/application.js:

var env = process.env.NODE_ENV || 'development';

So the default is development, and the default behaviour on an unhandled error is to send the stack trace to whoever triggered it. This is Express’s analogue of Django’s DEBUG, with one difference worth knowing: Express leaks less — a stack trace, not the settings object and local variables. It is still absolute paths, dependency names and versions, and a map of your internal structure.

the fix
Terminal window
NODE_ENV=production node server.js

The value must be exactly production. Production, prod, and an unset variable all leave you in development mode, and none of them warn.

Add an error handler so you control the response rather than relying on the absence of a leak:

// last middleware — four arguments is what marks it as an error handler
app.use((err, req, res, _next) => {
req.log?.error({ err }); // full detail server-side
res.status(err.status || 500).json({ error: "Internal Server Error" });
});

The four-argument signature is load-bearing. An error handler written with three parameters is registered as ordinary middleware, never receives the error, and the default handler runs instead — silently.

verify it workedRun this in: http response
Terminal window
curl -s https://example.com/route-that-throws | head -5

You want the status text, not a stack. Test a route that genuinely throws rather than a 404, because 404s take a different path and will look fine either way.

before you ship this

Setting NODE_ENV=production does more than change error output. npm install skips devDependencies, Express enables view caching, and some libraries change behaviour on the same variable. That is all desirable in production and surprising if you set it locally to test this one behaviour.

If your platform sets NODE_ENV for you, check what it actually sets rather than assuming — this is a common source of “it leaked in staging but not production”.

settings on this page
NODE_ENVenv

Express reads process.env.NODE_ENV or 'development', and the default error handler sends the stack trace in every environment except production. So an unset variable leaks stack traces, and the leak is invisible in local development because it looks identical there.

accepts
"production" | <anything else>
default
unset — Express falls back to "development"
set in
process environment
Read in: express 5.2.1 lib/application.js
if you see this
Error: Cannot set headers after they are sent to the client

The response was already sent when the error handler tried to send another. Usually a handler that responds and then calls next(err), or one that forgets to return after responding — the original response still reaches the client, so the failure shows up only in the log.

Read in: Node.js ERR_HTTP_HEADERS_SENT — reproduced on Express 5.2.1