Graceful Shutdown
Objectives
By the end of this lesson, you should be able to:
- Explain what SIGTERM is, and when a process receives it
- Stop a server from accepting new connections while letting in-flight requests finish
- Explain why an abrupt process exit during a deployment is a real problem for real users
💡 Why this matters: Every deployment, restart, or scale-down event sends a running process a signal asking it to stop. A process that exits immediately drops every request it was in the middle of handling, a process that shuts down gracefully finishes them first, this is the difference a real user notices as a failed request during a routine deploy.
⚠️ A note on verification: every snippet and every output in this lesson was actually run.
What SIGTERM Is
SIGTERM is a signal, sent by the operating system, a process manager, or a deployment platform, asking a process to terminate, politely, it’s a request, not a kill, a process that ignores it will eventually receive SIGKILL, which cannot be intercepted or handled at all. Handling SIGTERM correctly is a process’s one chance to clean up before it’s forced to stop.
A Server That Shuts Down Gracefully
const express = require('express');
const app = express();
app.get('/slow', (req, res) => {
setTimeout(() => {
res.json({ status: 'completed slow request', pid: process.pid });
}, 800);
});
const server = app.listen(6001, () => console.log(`Server ${process.pid} listening`));
process.on('SIGTERM', () => {
console.log('SIGTERM received, starting graceful shutdown');
server.close(() => {
console.log('All in-flight requests finished, server closed');
process.exit(0);
});
});
server.close() does two things: it stops the server from accepting any new connections immediately, but it doesn’t force existing, in-flight requests to stop, its callback only fires once every request that was already being handled has actually finished.
Confirming It Actually Waits
sending SIGTERM to 7
SIGTERM received, starting graceful shutdown
{"status":"completed slow request","pid":7} <- slow request finished
All in-flight requests finished, server closed
SIGTERM was sent while /slow was still in its 800ms setTimeout, the process didn’t exit immediately, server.close()’s callback, and the eventual process.exit(0), only ran after that in-flight request actually completed and sent its response. A client mid-request during a deploy gets a real, successful response, not a dropped connection.
Why This Matters During Deployment Specifically
A rolling deploy, replacing old instances with new ones, sends SIGTERM to every old instance as part of the normal process, this happens routinely, not just during incidents. Without a graceful shutdown handler, every deploy would drop whatever requests happened to be in flight at that exact moment, with one, deploys become invisible to users, exactly the property a production deployment needs.
Try It
- Build the server above, send it a request to
/slow, then sendSIGTERMshortly after, and confirm the request completes before the process exits. - Add a timeout, forcing
process.exit(1)ifserver.close()hasn’t finished within, say, 10 seconds, and explain why a graceful shutdown handler should never wait forever. - Add the same
SIGTERMhandler to a cluster worker (Lesson 2), and confirm it still finishes in-flight requests before that specific worker exits. - Explain, in one or two sentences, the difference between
SIGTERMandSIGKILL, and why only one of them can be handled in code.
Recap
SIGTERMasks a process to stop, giving it a chance to clean up first,SIGKILLforces termination immediately and can’t be intercepted.server.close()stops new connections while letting in-flight requests finish, confirmed here with a slow request completing afterSIGTERMwas already sent.- Graceful shutdown is what makes routine deploys invisible to real users, not just a defense against unexpected crashes.
Next lesson: PM2, a process manager that restarts a crashed process automatically, tying process management together into something that runs unattended.