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”.