How to Handle Graceful Shutdown in Node.js
This article explains how to implement graceful shutdowns in Node.js applications when they receive termination signals like SIGTERM or SIGINT. You will learn why graceful shutdowns are critical for maintaining data integrity and how to properly close database connections, stop accepting new HTTP requests, and safely exit the process using Node.js event listeners.
What is a Graceful Shutdown?
A graceful shutdown occurs when an application process is commanded to stop, but instead of exiting immediately, it completes its ongoing tasks, closes open connections, saves its state, and then exits. This prevents data corruption, ensures users do not experience abrupt request failures, and allows orchestrators like Kubernetes or PM2 to manage container lifecycles smoothly.
Understanding SIGTERM and SIGINT
Operating systems send signal events to running processes to control their lifecycle:
- SIGTERM: Sent by container orchestrators (like Kubernetes), process managers (like PM2), or system daemons to request a polite termination. The process is expected to clean up and exit.
- SIGINT: Typically triggered when a user presses
Ctrl+Cin the terminal. It interrupts the process, requesting a shutdown.
Step-by-Step Implementation
To handle a graceful shutdown in a Node.js application, follow these three steps.
1. Listen for the System Signals
Node.js allows you to listen for system signals using the global
process object.
process.on('SIGTERM', () => {
console.log('SIGTERM signal received: closing HTTP server');
handleShutdown();
});
process.on('SIGINT', () => {
console.log('SIGINT signal received: closing HTTP server');
handleShutdown();
});2. Close the HTTP Server
When a shutdown is initiated, you must immediately stop the server
from accepting new incoming connections while allowing active requests
to finish processing. If you are using Express or the native
http module, use server.close().
const express = require('express');
const app = express();
const server = app.listen(3000, () => {
console.log('Server is running on port 3000');
});
function handleShutdown() {
server.close(() => {
console.log('HTTP server closed.');
// Stop database connections and exit
cleanupAndExit();
});
}3. Clean Up Resources and Exit
Once the HTTP server stops accepting new connections, close any
persistent connections such as databases (e.g., MongoDB, PostgreSQL,
Redis). Once completed, exit the process with a success status code of
0.
async function cleanupAndExit() {
try {
// Example: Close database connection
await database.close();
console.log('Database connection closed successfully.');
process.exit(0);
} catch (error) {
console.error('Error during database disconnection:', error);
process.exit(1);
}
}Implementing a Forceful Timeout Fallback
Sometimes, a stuck database query or an infinite loop might prevent your cleanup code from finishing. To prevent the process from hanging indefinitely, implement a safety timeout that forces the process to exit if the graceful shutdown takes too long.
function handleShutdown() {
// Force exit after 10 seconds if graceful shutdown fails
setTimeout(() => {
console.error('Could not close connections in time, forcefully shutting down');
process.exit(1);
}, 10000);
server.close(() => {
console.log('HTTP server closed.');
cleanupAndExit();
});
}