Graceful shutdown and handling process signals
The moment a deploy or a restart kills a Node process, it may be in the middle of handling real requests — graceful shutdown is the deliberate practice of finishing that in-flight work before actually exiting, instead of dropping it on the floor.
3 min read
What happens by default when a process is asked to stop
A deploy, a container orchestrator scaling down, or a manual restart sends
the process a SIGNAL — typically SIGTERM ("please stop") — and by default,
if nothing handles it, Node exits close to immediately, mid-request,
whatever it happened to be doing at that exact moment
Process managers and container orchestrators (covered at a higher level in this platform's Cloud & Serverless Infrastructure domain) don't just kill a process outright during a normal deploy or scale-down — they send SIGTERM, a signal that conventionally means "please shut down," giving the process a real opportunity to clean up before actually exiting. Without any explicit handling, Node's default behavior on SIGTERM is to exit close to immediately — which means any request genuinely in progress at that exact moment gets cut off mid-response, a real, user-visible failure during what should be a routine, harmless deploy.
Listening for SIGTERM and shutting down deliberately
const server = app.listen(3000);
process.on("SIGTERM", () => {
console.log("SIGTERM received, shutting down gracefully");
server.close(() => { // stops accepting NEW connections, but lets EXISTING ones finish
console.log("all in-flight requests finished, exiting");
process.exit(0);
});
});server.close() (from the underlying http server, whether reached directly or through Express) stops accepting new incoming connections immediately, but doesn't forcibly terminate connections already in progress — it waits for those in-flight requests to actually finish, then calls the callback, at which point it's genuinely safe to exit. This is the real mechanism behind "graceful": no new work starts, but existing work is allowed to complete cleanly, instead of being abruptly severed mid-response.
A real, necessary safety net: a timeout for shutdown that hangs
process.on("SIGTERM", () => {
server.close(() => process.exit(0));
setTimeout(() => {
console.error("forced shutdown — graceful close took too long");
process.exit(1); // FORCE exit, even if some request never actually finished
}, 10_000); // a real, deliberate upper bound on how long shutdown is allowed to take
});If a request genuinely hangs (a stuck database query, a slow downstream API with no timeout of its own) server.close()'s callback might never fire, leaving the process running indefinitely instead of actually shutting down — a real, practical problem for an orchestrator that expects a process to exit within some reasonable window after SIGTERM, and will eventually send the much harsher SIGKILL (which cannot be caught or handled at all) if the process doesn't exit in time. A deliberate timeout that force-exits after a bounded wait is the standard, necessary safety net against a graceful shutdown that itself never actually completes.
Cleaning up more than just the HTTP server: database connections, queues, timers
process.on("SIGTERM", async () => {
server.close();
await dbPool.end(); // close the database connection pool cleanly, not abruptly
clearInterval(heartbeatTimer); // stop any recurring timers (this domain's memory-leaks lesson's own concern)
process.exit(0);
});A real application typically holds more open resources than just the HTTP server itself — a database connection pool (from the earlier pooling lesson), a message queue connection, recurring timers — and graceful shutdown means cleaning up each of these deliberately, not just the server, since an abruptly-killed database connection can leave the database itself with stale, unclosed connection state to clean up on its own end. This is the same "always pair setup with matching cleanup" principle the JavaScript Fundamentals domain's memory-leaks lesson argued for, applied here to an entire process's shutdown rather than a single component's lifecycle.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What happens by default when Node receives SIGTERM, with no explicit handler registered?
2. What does server.close() actually do when called inside a SIGTERM handler?
3. Why does a graceful shutdown handler need a force-exit timeout as a safety net?