You write a graceful shutdown handler in Node.js, Python, or Go. You catch SIGTERM and SIGINT, close your database connections, flush open files, and exit cleanly.
Then someone runs kill -9 <pid> (or Kubernetes kills your container after a timeout).
Your cleanup handlers never run. Your open files aren't flushed. Your database connection drops abruptly.
Why? Why can't a process catch, block, or ignore SIGKILL? What actually happens inside the Linux kernel when signal 9 is delivered?
1. How Regular Signals Work vs SIGKILL
To understand why SIGKILL is unstoppable, you first have to understand how standard signals (like SIGTERM or SIGINT) work.
Every Linux process has a struct task_struct in kernel space. Inside that struct lives a signal handler table (struct sighand_struct):
[User Space Process]
▲
│ 3. Kernel switches execution to your custom handler
[Linux Kernel]
▲
│ 2. Kernel checks sighand_struct -> points to your function
sys_kill(pid, SIGTERM)
│ 1. Signal posted to pending queue
When you call signal(SIGTERM, handler) or signal.signal(signal.SIGTERM, my_func):
- You register a memory address pointing to your custom function inside your process's virtual memory.
- When
SIGTERMarrives, the kernel queues it in the process's pending signal mask. - On the next context switch back to user space, the kernel sets up a stack frame and redirects the CPU to execute your handler.
Your application code is in charge of responding.
2. The SIGKILL Exception in Kernel Space
SIGKILL (signal 9) and SIGSTOP (signal 19) operate on a completely different path.
When kill(pid, 9) is invoked, it enters the kernel via sys_kill() and reaches complete_signal() in kernel/signal.c.
The kernel checks the signal number:
/* Simplified Linux Kernel Logic in kernel/signal.c */
if (sig == SIGKILL) {
/* 1. Bypass sighand_struct entirely */
/* 2. Mark the thread group with SIGNAL_GROUP_EXIT */
/* 3. Immediately set task state to TASK_DEAD / exit */
do_group_exit(sig);
}
The key architectural differences:
| Feature | Standard Signal (SIGTERM / 15) |
Force Kill (SIGKILL / 9) |
|---|---|---|
| Handling | Process user-space handler runs | Bypasses user space completely |
| Blockable? | Yes (via sigprocmask) |
No (Kernel ignores mask) |
| Catchable? | Yes (signal() / sigaction()) |
No (Kernel returns EINVAL) |
| Cleanup | Flushes buffers, closes DB pools | Instant kernel task destruction |
| Delivery | Delivered on next return to user-space | Executed immediately by scheduler |
3. What Happens to Resources on SIGKILL?
Because user-space code never runs again, developers often worry about resource leaks. Here is what the Linux kernel actually guarantees:
- Virtual Memory Reclaimed: All page tables and virtual memory regions allocated to the process are freed immediately.
-
File Descriptors Closed: The kernel calls
close()on all open file descriptors. Sockets send aFINorRSTpacket to remote endpoints. - Locks Released: POSIX file locks held by the process are automatically released.
What is NOT cleaned up:
- Unwritten User Buffers: Data sitting in application-level buffers (e.g. standard I/O streams not yet flushed to disk) is lost.
-
Temporary Files: Files created on
/tmpwithout an auto-delete wrapper stay on disk. - Database Transactions: In-flight database transactions are aborted by the database server when the socket closes, triggering rollback.
4. Practical Takeaways for Docker and Kubernetes
Understanding SIGKILL explains how container termination lifecycles work:
-
The Grace Period: When Kubernetes stops a pod, it sends
SIGTERMfirst. -
The Countdown: The application has
terminationGracePeriodSeconds(default: 30s) to catchSIGTERM, finish in-flight requests, and exit. -
The Guillotine: If the process is still alive after the grace period, the container runtime sends
SIGKILL.
docker stop -> SIGTERM (15) -> [ 10s grace period ] -> SIGKILL (9)
Golden Rule:
- Always listen for
SIGTERMto perform graceful cleanup. - Never design a system that relies on catching
SIGKILL— by POSIX design and kernel mechanics,SIGKILLbelongs to the operating system, not your application.
Top comments (1)
The diagram of the handler-table path is the part most SIGKILL explainers skip — SIGTERM is a request delivered through your process, SIGKILL is the kernel deleting the task from outside it. One footnote worth adding from the ops side: the closest thing to catching it is the parent watching with waitpid/WIFSIGNALED, which is why supervising processes (systemd, container runtimes) can still clean up after a SIGKILL even though the victim cannot. And the zombie edge case people hit in practice is usually the reverse problem — SIGKILL to a child whose parent never reaps.