What graceful shutdown actually protects
Containers, process managers, and platforms commonly send SIGTERM before replacing an instance. If the process exits immediately, a request may lose its response, a database write may be interrupted, and a queued job may run twice. A graceful shutdown gives the process a bounded interval to hand off traffic and finish the work it has already claimed.
Traffic
Readiness becomes false so a load balancer sends new requests elsewhere.
In-flight work
Existing HTTP responses, streams, and safe background work get time to complete.
Deadline
A firm timeout guarantees a stuck dependency cannot leave a deployment hanging.
Do not confuse shutdown with error recovery. A request that arrives after draining begins should receive a quick retryable response, while a request already in progress should be allowed to finish where possible. For handling unexpected application failures, pair this approach with clear API errors and the protections in our Node.js API rate-limiting guide.
Stop accepting new HTTP work first
Keep readiness separate from liveness. A process can be alive but not ready to receive traffic while it drains. On SIGTERM, mark the service as draining, return a temporary response from readiness probes, then ask the HTTP server to close. Closing a server stops new connections while allowing existing requests to complete.
let draining = false;
const server = app.listen(process.env.PORT || 3000);
app.get("/ready", (_req, res) => {
res.status(draining ? 503 : 200).json({ ready: !draining });
});
process.once("SIGTERM", async () => {
draining = true;
const forceExit = setTimeout(() => process.exit(1), 25_000);
server.close(async (error) => {
if (error) console.error("HTTP server close failed", error);
await closeDependencies();
clearTimeout(forceExit);
process.exit(error ? 1 : 0);
});
});Set the force-exit value below the termination grace period configured by your host. Also consider keep-alive connections: set sensible server timeouts and test slow clients, long polling, and streamed responses. A graceful path must be bounded, not an indefinite wait for every client.
Drain database, queue, and outbound resources deliberately
HTTP is only one boundary. Database pools, message consumers, WebSockets, telemetry exporters, and scheduled workers need their own shutdown behavior. Stop claiming new jobs before completing active jobs; otherwise a replacement worker can duplicate work. Make job handlers idempotent so a forced termination is still safe to retry.
Background processing deserves the same operational design as HTTP. Our Node.js background jobs and queues guide covers idempotency, retries, and observability in more depth. Avoid starting a cron task in every web process unless the task has a distributed lock or a dedicated worker.
Match the sequence to your deployment platform
In Kubernetes, use a readiness probe and make the pod unready before its grace period expires. In a managed container platform, confirm the signal, grace period, and load-balancer deregistration behavior rather than assuming they match local development. During rolling releases, keep enough healthy capacity to serve traffic while old instances drain.
- Set a termination grace period longer than your normal request and dependency timeouts.
- Use a pre-stop or readiness transition only when it is supported and measurable in your platform.
- Handle both
SIGTERMand local interruption signals during development. - Do not call
process.exit()before close callbacks and cleanup promises settle. - Test a deployment while requests, uploads, and queue jobs are active.
Make shutdown behavior observable
Log one structured event when draining starts and another when it completes. Record the reason, instance version, active-request count, elapsed drain time, forced-exit count, and failed cleanup operations. These signals reveal whether releases are dropping traffic or whether a dependency is regularly preventing clean exits.
Production safety is a system of small, verifiable boundaries. For the request-side protections that help keep overloaded services useful during a rollout, review the Node.js API rate-limiting guide as well.
Node.js shutdown release checklist
✓ Readiness is independent from liveness
✓ SIGTERM marks the instance draining before server close
✓ New requests are rejected or routed elsewhere promptly
✓ In-flight requests have a bounded time to finish
✓ Queue consumers stop claiming new work first
✓ Database, cache, and telemetry clients close in order
✓ A force-exit deadline is below the platform grace period
✓ Logs and metrics show each drain and forced exit
Ship Node.js releases without avoidable disruption
Endurance Softwares helps teams build secure, observable Node.js and Next.js systems with practical architecture and delivery practices.