Linux Process Signals Explained Simply (SIGTERM, SIGKILL, SIGHUP)
Learn Linux process signals every DevOps engineer must know: SIGTERM, SIGKILL, SIGHUP, SIGINT. How kill and pkill work, why Kubernetes uses SIGTERM, and how to handle signals in your app.
When a Kubernetes pod is terminated, when you press Ctrl+C in a terminal, or when nginx reloads its config without restarting — signals are what's happening under the hood. If you're running production workloads and don't understand signals, you're flying blind.
Here's everything you need to know.
What Is a Signal?
A signal is a software interrupt — a way for the OS (or another process) to notify a running process that something happened. The process can choose to handle the signal, ignore it, or let the default behavior take over.
Think of it like knocking on a door vs kicking it down. SIGTERM is a polite knock ("please shut down"). SIGKILL is kicking the door down — no choice.
The Signals You'll Use Daily
SIGTERM (signal 15) — Graceful Shutdown
The standard "please stop" signal. When the kernel or a user sends SIGTERM, the process receives it and can:
- Finish processing the current request
- Flush writes to disk
- Close database connections
- Remove lock files
- Then exit cleanly
kill 1234 # sends SIGTERM to PID 1234 (default)
kill -15 1234 # same thing, explicit
kill -TERM 1234 # same thing, by nameThis is what you should always try first. Most well-written services handle it.
SIGKILL (signal 9) — Force Kill
SIGKILL cannot be caught, blocked, or ignored by the process. The kernel forcefully terminates it immediately. No cleanup, no flush, no graceful anything.
kill -9 1234 # sends SIGKILL
kill -KILL 1234 # sameUse SIGKILL only when the process is frozen, stuck in a loop, or ignoring SIGTERM. Using -9 as a default (which many beginners do) is bad practice — it can corrupt data, leave stale lock files, and break things downstream.
SIGHUP (signal 1) — Reload Config
Originally meant "the terminal disconnected." Modern daemons repurpose it to mean "reload your configuration file without restarting."
kill -HUP $(cat /var/run/nginx.pid)
# or
pkill -HUP nginxAfter receiving SIGHUP, nginx re-reads nginx.conf and applies changes without dropping connections. Same for sshd, rsyslog, and most system daemons.
SIGINT (signal 2) — Keyboard Interrupt
This is what Ctrl+C sends to the foreground process. Most programs handle it like SIGTERM — they clean up and exit. Some scripts don't handle it and just die.
# In a script, trap SIGINT to clean up:
trap 'echo "Interrupted, cleaning up..."; rm -f /tmp/mylock; exit 1' INTSIGUSR1 / SIGUSR2 (signals 10 and 12) — Custom Use
These have no predefined meaning. Application developers use them for custom behavior:
SIGUSR1to nginx: reopen log files (useful after log rotation)SIGUSR1to some Go apps: print goroutine dumpSIGUSR2to some apps: toggle debug logging
kill -USR1 $(pgrep nginx)SIGPIPE (signal 13) — Broken Pipe
Sent when a process tries to write to a pipe whose read end is closed. Common cause of "Broken pipe" errors in shell scripts. Usually indicates the consumer closed before the producer finished.
kill vs pkill vs killall
# kill: by PID
kill -TERM 1234
# pkill: by process name (partial match)
pkill -TERM nginx # sends SIGTERM to all nginx processes
pkill -9 zombie-process # force kill by name
# killall: by exact process name
killall -HUP nginx
# Check what you're targeting before killing:
pgrep -l nginx # list PIDs matching "nginx"
pkill -e --dry-run nginx # (not all versions support --dry-run)Why Kubernetes Sends SIGTERM Before SIGKILL
When you delete a pod or a deployment scales down, Kubernetes doesn't immediately SIGKILL the container. It follows this sequence:
- Pod enters
Terminatingstate - Kubernetes removes the pod from the Service endpoints (traffic stops routing to it)
- Kubernetes sends SIGTERM to PID 1 in the container
- Kubernetes waits for
terminationGracePeriodSeconds(default: 30 seconds) - If the container is still running after the grace period, Kubernetes sends SIGKILL
This gives your app time to finish in-flight requests before dying. If your app ignores SIGTERM or doesn't exit fast enough, you'll get abrupt kills and potentially failed requests.
Handling SIGTERM in Your App
Python:
import signal
import sys
def handle_sigterm(signum, frame):
print("Received SIGTERM, shutting down gracefully...")
# close DB connections, flush cache, etc.
sys.exit(0)
signal.signal(signal.SIGTERM, handle_sigterm)
# Your app runs here
while True:
process_job()Node.js:
process.on('SIGTERM', () => {
console.log('SIGTERM received, closing HTTP server...');
server.close(() => {
console.log('HTTP server closed');
process.exit(0);
});
});The PID 1 Problem in Docker
In Docker, if your container runs a shell script that starts your app, the app gets PID 2+, not PID 1. Signals sent to PID 1 (the shell) don't automatically forward to the app.
Fix: use exec in your entrypoint script so the app replaces the shell as PID 1:
#!/bin/sh
# Entrypoint script
exec python /app/server.py # app becomes PID 1Or use the JSON array form in your Dockerfile:
ENTRYPOINT ["python", "/app/server.py"]
# NOT: ENTRYPOINT python /app/server.py (this runs via /bin/sh -c)Quick Reference
| Signal | Number | Can be caught? | Common use |
|---|---|---|---|
| SIGTERM | 15 | Yes | Graceful shutdown |
| SIGKILL | 9 | No | Force kill (last resort) |
| SIGHUP | 1 | Yes | Reload config |
| SIGINT | 2 | Yes | Ctrl+C |
| SIGPIPE | 13 | Yes | Broken pipe |
| SIGUSR1 | 10 | Yes | App-defined custom action |
| SIGUSR2 | 12 | Yes | App-defined custom action |
Understanding signals turns "the container died" from a mystery into a debuggable event. Check dmesg or journalctl for OOM kills (SIGKILL from the kernel), check your app logs for SIGTERM handling, and always make sure PID 1 in your containers is your actual process.
Today I Fixed
Short real fixes from production — posted daily
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
Build a Kubernetes Cluster with kubeadm from Scratch (2026)
Step-by-step guide to building a real multi-node Kubernetes cluster using kubeadm — no managed services, no shortcuts.
What is eBPF? Explained Simply for DevOps Engineers (2026)
eBPF lets you run custom code inside the Linux kernel safely — without writing kernel modules or rebooting. It's why Cilium is fast, why Datadog Agent is lightweight, and why the future of Kubernetes networking looks different. Here's what it actually is.
What Are Linux cgroups and Namespaces? The Foundation of Containers Explained
Docker and Kubernetes containers are built on Linux cgroups and namespaces. Understanding these fundamentals helps you debug container issues and set resource limits properly.