Graceful shutdown implementation and zero-downtime deployments?
process lifecycle, signal handling, and connection draining.
listen for SIGTERM, stop accepting new connections, drain in-flight requests, close resources, exit.
SIGNAL HANDLING
When a container is stopped (e.g., during rolling deployment), Docker or Kubernetes sends a SIGTERM signal. If your app ignores it, Kubernetes waits 30 seconds then sends SIGKILL, forcing termination. In-flight requests are dropped. Graceful shutdown handles SIGTERM, closes the server, and exits cleanly within the grace period.
CLOSE VS SHUTDOWN
server.close() stops accepting new connections but allows existing connections to finish. This is safe and preserves in-flight requests. Process.exit() forcefully terminates all connections immediately. Always use close() when possible.
SHUTDOWN SEQUENCE
On SIGTERM, the handler calls server.close(). The server stops accepting new incoming connections. Requests already in progress continue. When all requests complete, the close event fires. In the close handler, clean up resources: close database connections, flush logs, cancel timers. Then call process.exit(0).
TIMEOUT AND FORCE EXIT
If requests take too long, the shutdown could hang forever. Set a timeout: after 30 seconds, force exit. This is a safety net. For example: setTimeout(() => process.exit(1), 30000) ensures the process dies even if requests don't complete.
CONTAINERIZED ENVIRONMENT
In Docker or Kubernetes, rolling deployments replace containers gradually. During replacement, the old container receives SIGTERM and should drain requests. The new container starts and begins serving traffic. If the old container ignores SIGTERM and doesn't drain, its requests fail. Coordinating graceful shutdown with the deployment strategy prevents error spikes.
LOAD BALANCER COORDINATION
In cloud deployments, a load balancer routes traffic. Graceful shutdown should happen in phases. First, notify the load balancer to stop sending new requests (this is automatic with Kubernetes; the container removes itself from the service endpoint). Wait for existing requests. Then exit. This prevents the scenario where the load balancer routes a request to a dead container.
EXAMPLE IMPLEMENTATION
process.on('SIGTERM', () => { console.log('SIGTERM received, draining...'); server.close(() => { console.log('All connections drained. Exiting.'); process.exit(0); }); setTimeout(() => { console.error('Timeout, forcing exit.'); process.exit(1); }, 30000); });
Read the original → expressjs.com
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.