Graceful shutdown on SIGTERM
server.close() only stops new connections; a graceful shutdown drains everything else before SIGKILL does.
Predict first
Before you read: predict
ExamHall's proctoring gateway handles SIGTERM with server.close(() => process.exit(0)) and nothing else. The grading service calls it through a keep-alive agent on one connection: at SIGTERM a request on that connection is half a second from done, and the service sends its next request on the same connection as soon as each answer arrives. Each request takes 1 s; keepAliveTimeout is Node's default, 5 s. The grace period is 30 s, on this lesson's Kubernetes model.
What happens?
The 30-second version
Kubernetes sends SIGTERM, keeps routing to the pod briefly, and sends SIGKILL when the grace period ends. Keep serving until routing catches up, then stop accepting and drain what is in flight: requests, busy keep-alive connections, WebSockets, jobs. Give the drain its own deadline inside the grace period.
server.close() only stops new connections; a graceful shutdown drains everything else before SIGKILL does.
How it works
Step 1 of 13
In the kitchen
- the closing announcement, then the security lock at six sharp → SIGTERM, then SIGKILL when the grace period ends (step 1)
Analogy: A botanic garden at closing time
The tannoy announces closing; at six sharp the security firm locks the gates, whoever is still inside.
| At the botanic garden | In Node and Kubernetes |
|---|---|
| the closing announcement, then the security lock at six sharp | SIGTERM, then SIGKILL when the grace period ends (step 1) |
| coaches already on the road, sent by a website that still says "open" | requests routed after the signal (step 3) |
| the gate staff keep selling tickets for ten minutes, then shut the turnstiles | keep serving, then server.close() (steps 4–5) |
| a family on the land train, riding lap after lap | a busy keep-alive connection (step 6) |
| telling the driver to make this lap the last circuit | asking a busy connection to leave (step 7) |
| a photography club in the rose garden at sunset | WebSockets (step 8) |
| the café finishing the orders it has already taken | await worker.close() (step 9) |
| the head gardener's own sweep at ten to six, so the cash-up is done before the lock | your own deadline (step 10) |
| the car-park barrier, which every visitor's car must pass to leave | shared clients (step 11) |
In real terms: before you write the handler, list everything that can still be in progress at SIGTERM (requests, keep-alive connections, sockets, jobs, timers) and say what ends each one.
Breaks down when: visitors hear the announcement and walk out by themselves; nothing in a Node process reacts to SIGTERM unless your handler makes it.
Contrasting case: a batch job that exits when its work is done holds nothing between runs, so stopping it between runs loses nothing; a long-lived server always has something in flight.
Examples, run for real
4.1 ExamHall: the gateway that always waited for the kill
EXH-5000: ExamHall's proctoring gateway drained its last submit within seconds of SIGTERM, yet every deploy ended in SIGKILL at the end of the grace period. A 10-second metrics setInterval kept the process alive. MR !5300 clears it in the handler.
4.1 proctoring gateway: one submit in flight (4 s), a 10-second metrics setInterval, grace 30 s
as shipped (the handler closes the server) submit answered · 'Timeout' entries in getActiveResourcesInfo() after the drain: 1 · SIGKILL at 30 s
MR !5300 (it also clears the interval) submit answered · 'Timeout' entries in getActiveResourcesInfo() after the drain: 0 · exit 0 at 4 s, by itself
After the drain, process.getActiveResourcesInfo() still listed the interval's Timeout; with it cleared, the process exited by itself (step 12).
4.2 Media streaming: Node's default
4.2 media streaming: three segment requests in flight (3 s each) at SIGTERM, grace 30 s
no 'SIGTERM' listener (Node's default) answered 0 · cut 3 · ended by SIGTERM at once (exit code null, signal SIGTERM)
a listener that does nothing answered 3 · cut 0 · still running until SIGKILL at 30 s
With no listener the signal ends the process before any request finishes (a shell reports exit code 143, 128 + 15); a listener that does nothing keeps it alive until SIGKILL (steps 1–2).
Try it
Answer first, then check. Nothing is saved.
Question 1 of 6
A streaming catalogue API on Node 24 handles SIGTERM like this: wait 5 s, then server.close(() => process.exit(0)). Its callers are other services with keep-alive agents; on this lesson's routing model, a newly opened socket reaches a ready pod, but an open one stays on this pod. Four player-API clients each hold one socket and issue their next request as soon as the last answer arrives; each request takes 1 s, and at SIGTERM each is half a second from its next answer. Every deploy, the pod is killed at the end of its 30-s grace period, cutting 4 requests.
Which one change makes it exit 0 before the kill without cutting a request?
Question 2 of 6
A streaming API pod serves HTTP and runs a BullMQ worker for thumbnail jobs. Each request reads the title's manifest from Redis as it finishes, and each job writes its result through the same client as it finishes. At SIGTERM three clip requests are in flight, each needing 10 s more, the worker holds one job needing 20 s more, and the grace period is 30 s. On this lesson's routing model, the Service keeps sending the pod about one new request a second for 5 s after the signal.
Which shutdown order answers every request, refuses none, and completes the job on this pod?
Question 3 of 6
A streaming API keeps watch-time events in memory and writes them to the analytics store once a minute. Its SIGTERM handler waits 5 s, calls server.close() and arms a 28-s deadline that closes every connection, waits for the close callback, writes the buffered events, then exits 0. At SIGTERM it holds 120 events, and a creator's upload is in flight on a slow link that needs 80 s more. The grace period is 30 s, on this lesson's Kubernetes model.
What reaches the analytics store, and how does the pod end?
Question 4 of 6
A transcoding-status API on Node 24 handles SIGTERM with server.close(() => clearInterval(metrics)) and never calls process.exit(): it counts on Node exiting once nothing holds it. Besides the server and metrics, a 10-s setInterval, the process holds shipper, an open TCP connection to the log shipper, and retry, a setTimeout that re-sends a failed upload notice 6 s after SIGTERM. At SIGTERM one status request is in flight, 2 s from done. Every deploy ends in SIGKILL at the end of the 30-s grace period, on this lesson's Kubernetes model.
Which one change makes the pod exit 0 before the kill, with the request answered and the notice sent?
Question 5 of 6
A ticketing site's waiting room holds 40 fans on WebSockets on one pod. On SIGTERM the server waits 5 s, calls server.close() and then server.closeAllConnections(), and exits 0 in close's callback. The grace period is 30 s, on this lesson's Kubernetes model.
What happens to the fans' sockets?
Question 6 of 6
In the kubelet model of the Pod Lifecycle page, a ledger API pod has a preStop hook that sleeps 10 s, and terminationGracePeriodSeconds: 30. Its SIGTERM handler calls server.close(), waits for the callback for at most 25 s, then closes every connection and exits 0. When the pod is deleted, four postings are in flight; they would finish 8, 16, 24 and 32 s after the deletion.
What happens to the four postings?
Go deeper
Pro tips
- Test the shutdown with a real signal. In a test, start the server as a child process, send it SIGTERM while a request is in flight, and assert that the response arrives and how the process ends. When: any service with a SIGTERM handler. Verify (RAN, every graded script here): each forks its pod process and sends a real
SIGTERMwhile requests, sockets or a job are in flight, grading what each one got and how the process ended. - Log what still holds the process. At the end of the handler, log
process.getActiveResourcesInfo(): each entry is something still holding the event loop. When: a pod that always takes its whole grace period to stop (step 12).
Source
- Kubernetes docs, Pod Lifecycle, Termination of Pods: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/ · fetched 2026-10-18. "Once the grace period has expired, the KILL signal is sent to any remaining processes"; "The default terminationGracePeriodSeconds setting is 30 seconds"; "If the preStop hook is still running after the grace period expires, the kubelet requests a small, one-off grace period extension of 2 seconds"; "At the same time as the kubelet is starting graceful shutdown of the Pod, the control plane evaluates whether to remove that shutting-down Pod from EndpointSlice objects"; "Terminating endpoints always have their ready status as false" (steps 1, 3–4, 10). Valid as of 2026-10-18.
- BullMQ docs, Going to production, Gracefully shut-down workers (source
docs/gitbook/guide/going-to-production.md, branch master): https://docs.bullmq.io/guide/going-to-production · fetched 2026-10-18. "If a worker is killed without waiting for their jobs to complete, these jobs will be marked as stalled and processed automatically when new workers come online"; "the job may take longer time than the grace period for the server to restart" (steps 9–10). Valid as of 2026-10-18. - NestJS docs, Lifecycle events, Application shutdown (source
content/fundamentals/lifecycle-events.md): https://docs.nestjs.com/fundamentals/lifecycle-events · fetched 2026-10-18. "Shutdown hook listeners consume system resources, so they are disabled by default"; "To use shutdown hooks, you must enable listeners by calling enableShutdownHooks()" (step 13). Valid as of 2026-10-18. - Node.js v24 API, HTTP,
server.close(),server.closeAllConnections(),server.closeIdleConnections()(sourcedoc/api/http.md, branch v24.x): https://nodejs.org/docs/latest-v24.x/api/http.html · fetched 2026-10-18. "Stops the server from accepting new connections and closes all connections connected to this server which are not sending a request or waiting for a response"; changed in v19.0.0: "The method closes idle connections before returning" (steps 5–7). Valid as of 2026-10-18. - Node.js v24 API, process, Signal events (source
doc/api/process.md, branch v24.x): https://nodejs.org/docs/latest-v24.x/api/process.html · fetched 2026-10-18. "have default handlers on non-Windows platforms that reset the terminal mode before exiting with code 128 + signal number"; "If one of these signals has a listener installed, its default behavior will be removed" (step 2). Valid as of 2026-10-18.
Related lessons
Checked against the tools and docs as of 2026-10-18.