Implement graceful shutdown to drain active connections and complete in-flight requests without data loss or broken clients when restarting services.
When you deploy a new version of your service, your container orchestrator sends a SIGTERM signal and waits for the process to exit. Many teams ignore this signal and let the kernel force-kill the process, leaving behind disconnected clients, interrupted database transactions, and confused upstream services. Graceful shutdown—the practice of draining active connections and completing in-flight requests before exiting—is essential for production reliability. Without it, every deployment introduces brief failures and data inconsistency. For teams running Node.js services, implementing shutdown gracefully is straightforward but requires deliberate architecture.
The pattern works in three phases: signal interception, active request completion, and resource cleanup. When SIGTERM arrives, your process stops accepting new connections and logs the shutdown signal, then iterates through active requests, allowing each to complete within a timeout window (typically 30 seconds). During this window, external services continue routing traffic to your instance based on what they know; your server responds to existing requests but rejects new ones. Once all requests complete or the timeout expires, the process closes database connections, cleans up temporary files, and exits with code 0. This ensures that no client receives a connection reset and no database transaction is left hanging.
In Express, implement shutdown by storing server references and hooking the SIGTERM event: capture the HTTP server instance, listen for SIGTERM, call server.close() to stop accepting connections, wait for all active sockets to emit 'close', then close your database pools. For this pattern to work, set a reasonable keep-alive timeout on database connections and ensure that long-running operations (like file uploads) have their own timeout guards. Track active requests with a counter or middleware; when it reaches zero or your timeout expires, exit cleanly. One key tradeoff: waiting too long risks your orchestrator force-killing you after its own timeout (often 30 seconds for Kubernetes). Always set your graceful shutdown timeout shorter than your platform's termination grace period.
Start by adding a simple shutdown handler to your main application file that closes your HTTP server and database connections. Test shutdown locally by sending SIGTERM manually and verifying that in-flight requests complete. In production, monitor your deployment logs for unclean exits (exit code 137 or 143 indicates a force-kill) and adjust your termination grace period in your orchestrator if needed. For small teams, even basic shutdown handling—closing the HTTP server and waiting for active sockets—prevents most deployment-related errors and builds toward reliable production operations.