Python Graceful Shutdown: Handling SIGTERM Correctly
When Kubernetes stops a pod it sends SIGTERM first - if your Python worker ignores it, the runtime eventually sends SIGKILL and whatever job was in flight is lost. A one-time signal handler fixes it.
What SIGTERM means in Kubernetes
When you roll out a new version or scale down a deployment, Kubernetes sends
SIGTERM to each pod's main process and waits up to terminationGracePeriodSeconds
(default 30 s) for the process to exit on its own. Only after that deadline does it
send SIGKILL. A worker that ignores SIGTERM runs until the hard kill - any job
it was processing is dropped mid-flight.
The broken pattern
# worker.py - BUG: no SIGTERM handler
import time, os
RUNNING = True # never set to False
def process_job(job_id):
print(f"processing job {job_id}")
time.sleep(5) # simulates real work
print(f"done job {job_id}")
while RUNNING:
job = fetch_next_job()
process_job(job)
Send SIGTERM to this process and it keeps running. The only way to stop it is
SIGKILL, which gives it no chance to finish the current job.
The fix: register a SIGTERM handler
import signal, time, os
RUNNING = True
def _on_sigterm(signum, frame):
global RUNNING
print("SIGTERM received - finishing current job, then exiting", flush=True)
RUNNING = False
signal.signal(signal.SIGTERM, _on_sigterm)
def process_job(job_id):
print(f"processing job {job_id}")
time.sleep(5)
print(f"done job {job_id}")
while RUNNING:
job = fetch_next_job()
process_job(job) # current job runs to completion
# RUNNING is checked here, between jobs - not mid-job
The handler sets RUNNING = False immediately when the signal arrives, but the
while RUNNING: check only fires between jobs, so the in-flight job always
finishes. New jobs are not started after the flag flips.
Verify it works
python3 worker.py &
PID=$!
sleep 2
kill -TERM $PID # send SIGTERM
# worker logs the message, finishes current job, exits with code 0
wait $PID && echo "clean exit"
Compare that to the old behavior where ps -p $PID still showed the process
alive after kill -TERM and kill -9 was the only escape.
Production additions
preStophook - add a short sleep in a KubernetespreStoplifecycle hook so the load balancer has time to drain connections before SIGTERM arrives.- SIGINT too - register the same handler for
signal.SIGINTso Ctrl-C in dev behaves identically. - Async workers - in asyncio, use
loop.add_signal_handler(signal.SIGTERM, shutdown_callback)and cancel running tasks inside the callback. - Flush buffers - before exiting, flush any in-memory queues (logs, metrics, batched DB writes) to avoid silent data loss.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Registering a SIGTERM handler with signal.signal() in Python
- Using a shared RUNNING flag to drain a worker loop between jobs
- Verifying graceful shutdown with kill -TERM and wait
FAQ
Why does Python ignore SIGTERM by default?
Python does not ignore SIGTERM - it uses the default OS action, which is to terminate the process immediately. A worker loop that doesn't register a handler gets killed mid-job with no chance to finish. Registering a handler with signal.signal() gives you control over when the exit happens.
How do I handle SIGTERM in a Python worker loop?
Set a module-level RUNNING flag to True, register a signal.signal(signal.SIGTERM, handler) where the handler sets RUNNING = False, and check RUNNING at the top of the loop. The current job finishes; the loop exits cleanly on the next iteration.
How does Kubernetes stop a pod gracefully?
Kubernetes sends SIGTERM to the container's PID 1 and waits terminationGracePeriodSeconds (default 30 s). If the process exits before the deadline, Kubernetes calls it a clean stop. If not, it sends SIGKILL - so your process must react to SIGTERM and exit within that window.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →