recipes / graceful-shutdown / nextjs

Graceful shutdown with Next.js

The standalone server already handles SIGTERM. Add readiness and cleanup around it, do not replace it.

The Next.js production server installs its own SIGTERM and SIGINT handlers. It stops accepting connections, finishes in-flight requests and pending after() callbacks, then exits. Leave that in place and add what Kubernetes needs around it.

Readiness and cleanup

instrumentation.ts runs once when the server starts. Register extra listeners there; they run next to the built-in ones.

// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME !== "nodejs") return;

  const { state } = await import("./lib/state");

  for (const signal of ["SIGTERM", "SIGINT"] as const) {
    process.on(signal, () => {
      state.ready = false; // /api/readyz starts returning 503
      // Synchronous cleanup only: Next.js exits as soon as the server is closed.
    });
  }
}
// app/api/readyz/route.ts
import { state } from "@/lib/state";

export const dynamic = "force-dynamic";
export function GET() {
  return new Response(state.ready ? "ready" : "not ready", { status: state.ready ? 200 : 503 });
}

Pod spec

terminationGracePeriodSeconds: 30
containers:
  - name: web
    lifecycle:
      preStop:
        sleep:
          seconds: 5
    readinessProbe:
      httpGet: { path: /api/readyz, port: http }
      periodSeconds: 5

Notes

  • Need asynchronous cleanup, such as draining a queue? Set NEXT_MANUAL_SIG_HANDLE=true. Next.js then installs no handlers and you own the whole shutdown, including calling process.exit().
  • next start and the standalone server.js behave the same way. npm run start in the image does not: see run node directly.
  • Containerizing Next.js is its own recipe: Containerize a Next.js app.

See it applied

Updated 2026-09-10 · tags: nextjs, signals, shutdown · edit on GitHub