recipes / graceful-shutdown
Graceful shutdown on SIGTERM
Stop accepting connections, finish in-flight requests, then exit with a deadline.
Without a SIGTERM handler, Node.js exits immediately and in-flight requests fail.
With a handler that never finishes, Kubernetes waits for terminationGracePeriodSeconds
and then kills the process anyway. Do both: handle the signal, and set a deadline.
Handler
import { createServer } from "node:http";
const server = createServer(handler);
server.listen(process.env.PORT ?? 3000);
let shuttingDown = false;
function shutdown(signal) {
if (shuttingDown) return;
shuttingDown = true;
console.log(JSON.stringify({ level: "info", msg: `${signal} received, shutting down` }));
// 1. Stop accepting new connections, finish in-flight requests.
server.close((err) => process.exit(err ? 1 : 0));
// 2. Close idle keep-alive connections so close() can finish.
server.closeIdleConnections();
// 3. Hard deadline, shorter than terminationGracePeriodSeconds.
setTimeout(() => process.exit(1), 10_000).unref();
}
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);
Pod spec
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
lifecycle:
preStop:
sleep:
seconds: 5
Why the preStop sleep
Endpoint removal and SIGTERM happen in parallel. For a few seconds after the
signal, kube-proxy and ingress controllers may still route new requests to the Pod.
Sleeping in preStop delays the signal until routing has caught up. The sleep
action is available since Kubernetes 1.30; use exec with sleep 5 on older clusters,
which requires a shell in the image.
Notes
- Order of timeouts:
preStop+ app deadline <terminationGracePeriodSeconds. - Also fail the readiness probe as soon as shutdown starts. See health checks.
- Close database pools and flush logs after
server.close()resolves, beforeprocess.exit(). - Since Node.js 19,
server.close()closes idle keep-alive connections itself. The explicit call keeps older versions working. - Using a framework or a meta-framework? The recipes below cover Express, Fastify, Hono, Elysia, NestJS, Next.js and TanStack Start.
In this section
- Graceful shutdown with Express - app.listen() returns a plain Node.js http.Server. Close that, not the app.
- Graceful shutdown with Fastify - Use app.close() with forceCloseConnections and onClose hooks, and listen on 0.0.0.0.
- Graceful shutdown with Hono - Hono is runtime-agnostic. Close the server the adapter gave you, on Node or on Bun.
- Graceful shutdown with Elysia - Elysia runs on Bun. Call app.stop() on SIGTERM and ship it in the Bun distroless image.
- Graceful shutdown with NestJS - Turn on enableShutdownHooks() and use the lifecycle hooks to flip readiness and close resources in order.
- Graceful shutdown with Next.js - The standalone server already handles SIGTERM. Add readiness and cleanup around it, do not replace it.
- Graceful shutdown with TanStack Start and Nitro - Nitro's node server drains in-flight requests on SIGTERM. Tune its timeout and add readiness in the server entry.
See it applied
- examples/full/src/server.js in the repository
- examples/full - every recipe applied to one app
Updated 2026-09-10 · tags: signals, shutdown, http · edit on GitHub