Back to 20 Concepts
foundationsIntermediate

Process Signals & Zero-Downtime Graceful Shutdown

Graceful shutdown intercepts OS termination signals (SIGINT, SIGTERM) to stop accepting new requests, close database pools, finish in-flight HTTP requests, and exit cleanly with code 0.

Intuitive Mental Model

Closing a Restaurant at Night: The host locks the entrance door so no new diners enter, the kitchen finishes cooking orders currently on tables, and the staff turns off the gas stoves before leaving.

Node.js ESM / CJS ImplementationNode.js v22 LTS
import http from 'http';

const server = http.createServer((req, res) => {
  res.end('Hello World');
}).listen(3000);

async function gracefulShutdown(signal: string) {
  console.log(`Received ${signal}. Starting graceful shutdown...`);
  
  // 1. Stop accepting new HTTP connections:
  server.close(async () => {
    console.log('HTTP server closed. Finishing DB pools...');
    // 2. Close Database connections
    // await db.pool.end();
    // 3. Exit with success code:
    process.exit(0);
  });

  // Force kill if graceful shutdown hangs for > 10s:
  setTimeout(() => {
    console.error('Shutdown timed out. Forcefully terminating.');
    process.exit(1);
  }, 10000).unref();
}

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));

Key Architectural Takeaways

  • Kubernetes and Docker send SIGTERM first, wait terminationGracePeriodSeconds (default 30s), then send SIGKILL.
  • server.close() allows active in-flight requests to complete while immediately rejecting new connections.
  • .unref() on the fallback timeout timer ensures the timer itself does not keep the event loop alive.
Common Production Mistake

Calling process.exit(0) immediately inside the SIGTERM listener, abruptly severing active client connections mid-transaction.

Recommended Solution

Call server.close() first and wait for existing connections and database pools to drain.