Graceful Shutdown in Node.js
Containers and process managers stop your app by sending SIGTERM. If you exit immediately, requests in progress fail and database transactions are cut off. A graceful shutdown finishes the current work first.
The steps
- Stop accepting new connections.
- Let in-flight requests finish.
- Close resources: database pools, queues, caches.
- Exit, and force-exit if it takes too long.
Implementation
import http from 'node:http';
import { app } from './app.js';
import { pool } from './db.js';
const server = http.createServer(app);
server.listen(process.env.PORT ?? 3000);
let shuttingDown = false;
async function shutdown(signal) {
if (shuttingDown) return;
shuttingDown = true;
console.log(`${signal} received, shutting down`);
// Force exit if cleanup hangs
setTimeout(() => {
console.error('Shutdown timed out, forcing exit');
process.exit(1);
}, 25_000).unref();
server.close(async () => { // stops new connections, waits for open ones
await pool.end(); // close DB connections
console.log('Clean shutdown complete');
process.exit(0);
});
server.closeIdleConnections(); // drop idle keep-alive sockets right away
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
Report "not ready" during shutdown
app.get('/healthz/ready', (req, res) => res.status(shuttingDown ? 503 : 200).end());
In Kubernetes, the readiness probe failing takes the pod out of the load balancer, so no new traffic arrives while it drains.
Container gotchas
- Run
nodedirectly (CMD ["node", "server.js"]), not vianpm start, so the signal reaches your process. - Keep your timeout shorter than the orchestrator's grace period (30 seconds by default in Kubernetes).
- Long-running jobs should checkpoint progress so they can resume after a restart.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.