How do you implement graceful shutdown in a Node service?
3 minadvancednodejsgraceful-shutdownsigtermsignalscleanup
Quick Answer
Listen for SIGTERM/SIGINT, stop accepting new connections (server.close), finish in-flight requests, close resources like DB pools and message consumers, then exit. Add a timeout to force-exit if cleanup hangs. This prevents dropped requests and corrupted state during deploys and container restarts.
Detailed Answer
Answer:
Graceful shutdown ensures a deploy, scale-down, or Ctrl+C doesn't drop in-flight requests or leave resources dangling.
The sequence:
- Catch termination signals (
SIGTERMfrom orchestrators/containers,SIGINTfrom Ctrl+C). - Stop accepting new work —
server.close()stops new connections but lets in-flight requests finish. - Drain / close resources — DB pools, Redis, message-queue consumers, open files.
- Exit with code 0, or force-exit after a timeout if something hangs.
const server = app.listen(3000);
async function shutdown(signal) {
console.log(`${signal} received, shutting down...`);
// Stop accepting new connections; finish in-flight ones
server.close(async () => {
try {
await db.end(); // close DB pool
await redis.quit(); // close cache
process.exit(0);
} catch (err) {
console.error('Error during shutdown', err);
process.exit(1);
}
});
// Safety net: don't hang forever
setTimeout(() => {
console.error('Forced shutdown after timeout');
process.exit(1);
}, 10_000).unref();
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
Why it matters:
- Kubernetes sends
SIGTERMthen waits (terminationGracePeriodSeconds) beforeSIGKILL. HandlingSIGTERMlets running requests complete during a rolling deploy → zero dropped requests. - Closing DB/queue connections cleanly avoids leaked connections and half-processed messages.
.unref()on the timeout ensures it doesn't itself keep the process alive.
Tip: also fail readiness checks first so the load balancer stops routing new traffic before you close the server.