Signals and kill
Signals are asynchronous notifications the kernel delivers to processes. SIGTERM (15) asks politely and can be caught; SIGKILL (9) cannot be caught or ignored and skips all cleanup. Reach for SIGTERM first — SIGKILL is the last resort.
A signal is not a message with a payload — it is a tap on the shoulder from the kernel. The kernel queues a small integer at the process’s door, and the process either handles it with a registered function, ignores it, or lets the default action run. Most defaults are “terminate.” The four signals you will use daily are SIGTERM (15), SIGKILL (9), SIGINT (2), and SIGHUP (1). They look similar but behave very differently, and choosing the wrong one costs you data integrity, open connections, and clean log entries.
After this lesson you can send SIGTERM, SIGKILL, SIGINT, and SIGHUP by name and number using kill, pkill, and killall; explain why SIGKILL cannot be caught; describe what a signal handler does; and know when each of the four signals is appropriate.
Signals are integers. Each has a default action and can be overridden. The kernel delivers a signal by setting a pending-signal bit in the process’s task structure. On the next trip through the kernel scheduler, the pending signal is checked and dispatched.
A process can register a signal handler — a function that runs instead of the default action. It can also ignore a signal (SIG_IGN). The exceptions are SIGKILL (9) and SIGSTOP (19/17): these can never be caught, blocked, or ignored. The kernel always handles them directly.
Key signals and their defaults:
| Signal | Number | Default | Can catch? | Typical use |
|---|---|---|---|---|
| SIGHUP | 1 | Terminate | Yes | Reload config |
| SIGINT | 2 | Terminate | Yes | Ctrl-C |
| SIGQUIT | 3 | Core dump | Yes | Ctrl-\ |
| SIGKILL | 9 | Terminate | No | Force kill |
| SIGTERM | 15 | Terminate | Yes | Graceful stop |
| SIGSTOP | 19/17 | Stop | No | Pause process |
| SIGTSTP | 20/18 | Stop | Yes | Ctrl-Z |
| SIGCONT | 18/19 | Continue | Yes | Resume stopped |
kill PID sends SIGTERM (15) — the polite shutdown request. Despite the name, kill does not forcibly terminate anything by default. It sends SIGTERM, which the process can catch to run cleanup: flush write buffers, close database connections, finish in-flight requests, write a PID file tombstone.
kill 1843 # send SIGTERM to PID 1843
kill -15 1843 # same, explicit signal number
kill -SIGTERM 1843 # same, explicit signal name
kill -TERM 1843 # same, short nameIf the process has registered a SIGTERM handler, it runs that function. If not, the default action (terminate) runs immediately. Well-behaved daemons (nginx, postgres, redis) always catch SIGTERM and shut down cleanly.
kill -9 PID sends SIGKILL — the unconditional terminate. The kernel delivers SIGKILL directly without ever entering user space. No handler can intercept it. The process gets no opportunity to flush buffers, close connections, or clean up temp files.
kill -9 1843 # SIGKILL — immediate, no cleanup
kill -KILL 1843 # same, by nameWhen to use SIGKILL: only after SIGTERM has failed and the process is genuinely stuck (blocking in uninterruptible kernel code, ignoring SIGTERM, or already a zombie). The standard sequence:
kill 1843 # SIGTERM — give it time to clean up
sleep 5
kill -0 1843 2>/dev/null && kill -9 1843 # SIGKILL only if still alivekill -0 does not send a signal — it checks whether the process exists and you have permission to send it signals. Exit code 0 means it still exists.
pkill and killall send signals by process name, not PID. Useful when you don’t want to look up the PID first.
pkill nginx # SIGTERM to all processes named "nginx"
pkill -9 nginx # SIGKILL to all processes named "nginx"
pkill -u alice # SIGTERM to all processes owned by user alice
pkill -f 'python train.py' # match against the full command line
killall nginx # same as pkill nginx on Linux (BSD behaviour differs)pkill matches against the process name field (truncated to 15 chars on some kernels). -f matches the entire command line, which is more precise. Always test what pkill -l nginx (or pgrep nginx) matches before sending a signal to avoid surprises.
pgrep nginx # list PIDs that would be hit — no signal sent
pgrep -l nginx # list PIDs + namesSIGHUP (1) is the reload signal. Originally it meant “the terminal hung up.” Daemons repurpose it: when they receive SIGHUP they re-read their configuration file without restarting. This is how you tell nginx, sshd, or rsyslogd to pick up config changes without dropping connections.
kill -HUP $(pgrep nginx) # tell nginx to reload config
kill -1 $(cat /var/run/nginx.pid) # same via PID fileSIGINT (2) is what Ctrl-C sends to the foreground process group. The shell generates it and the kernel delivers it to every process in the foreground group simultaneously. A process that ignores SIGINT will not respond to Ctrl-C.
Gracefully stop a stuck web server, falling back to SIGKILL.
# Step 1: find the PID
pgrep -a gunicorn
# Step 2: try SIGTERM
kill $(pgrep gunicorn)
# Step 3: wait and check
sleep 10
pgrep gunicorn && echo "still alive"
# Step 4: if still alive, SIGKILL
kill -9 $(pgrep gunicorn)In practice, a well-written gunicorn catches SIGTERM, finishes in-flight requests, closes database connection pools, and exits cleanly. If it does not exit within 10–30 seconds (depending on your SLA), then SIGKILL is justified. The key point: never start with SIGKILL.
▸Why this works
On macOS, signal numbers differ for SIGSTOP and SIGCONT (SIGSTOP is 17, SIGCONT is 19 on macOS; reversed on Linux). Signal names always work portably. The pkill and pgrep commands are available on macOS but may not accept all the same flags. killall on macOS sends SIGTERM by default but behaves identically to Linux for basic usage.
▸Common mistake
kill -9 as the first and only tool is the most common signal mistake. SIGKILL skips graceful shutdown: open files may be left in an inconsistent state, database write-ahead logs may be left uncommitted, network connections are reset rather than closed gracefully (TCP FIN is never sent), and temp files accumulate. Always give a process a chance to clean up with SIGTERM first. If your service routinely requires SIGKILL to stop, that is a bug in the service — it is not handling SIGTERM.
Why can't a process catch or ignore SIGKILL?
Signals are integer notifications delivered by the kernel. SIGTERM (15) is the polite shutdown request — it can be caught, allowing graceful cleanup. SIGKILL (9) cannot be caught or ignored; the kernel terminates the process immediately with no cleanup. SIGINT (2) is what Ctrl-C sends to the foreground process group. SIGHUP (1) tells daemons to reload their configuration. Use kill PID for SIGTERM, kill -9 PID for SIGKILL; pkill NAME targets by process name; pgrep NAME shows which PIDs would be hit without sending anything. Always try SIGTERM first.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.