How do you implement graceful shutdown in a Node.js server?
Take a moment.
Say your answer out loud or write it down first — retrieval is what makes it stick. Reveal when you're ready.
Short answer
Listen for SIGTERM/SIGINT, stop accepting new connections (server.close), finish in-flight requests within a timeout budget, close downstream resources (DB, queues), then exit. Orchestrators give ~30s before SIGKILL.
Detailed explanation
Killing a server mid-request drops responses, corrupts batch jobs, and leaks connections. Graceful shutdown coordinates a sequence when platforms signal termination (deployments, scale-downs): first stop the intake — server.close() stops accepting new connections while existing ones finish (keep-alive connections need closing too); simultaneously flag load balancers/readiness probes unhealthy so traffic reroutes. Then wait for active requests with a hard deadline — typically force-exit after 20-30s since Kubernetes sends SIGKILL shortly after its grace period. Finally release dependencies in order: flush message queues/logs, close database pools, cancel timers/workers. Edge cases worth naming: long-running requests exceeding the window get cut (set per-request deadlines), WebSocket connections need explicit close codes, idempotency protects retried writes during the window. Libraries like terminus (@godaddy/terminus) package this correctly — implementing it raw demonstrates you understand every step.
Example
import http from 'node:http';
const server = http.createServer(handler);
server.listen(3000);
const connections = new Set();
server.on('connection', (conn) => {
connections.add(conn);
conn.on('close', () => connections.delete(conn));
});
async function shutdown(signal) {
console.log(`${signal} received — draining…`);
server.close(); // stop new requests
setTimeout(() => process.exit(1), 25_000).unref(); // hard deadline
await Promise.allSettled([
...[...connections].map((c) => new Promise((r) => c.end(r))),
closeDatabasePool(),
queue.flush(),
]);
process.exit(0);
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));Interview tip
Narrate the ordered checklist — intake off → drain → resources → exit — and mention the Kubernetes grace-period race. Naming terminus signals practical experience.
Common mistakes
- Forgetting keep-alive/idle connections hang server.close().
- No timeout fallback, so the process never exits.