Skip to main content

Signal

A signal is an asynchronous notification sent to a Linux process by the kernel, another process, or the user. Each signal has a number and name. A process can handle signals with custom code, ignore them, or let the default action (often termination) occur.

What Is a Signal in Simple Terms

Signals are the way processes communicate without passing data. They are simple notifications: "you should shut down", "reload your config", "you have been paused."

When you press Ctrl+C in a terminal, you are sending SIGINT to the running process. When you run kill 1234, you are sending SIGTERM. When you run systemctl reload nginx, systemd sends SIGHUP to nginx, which reloads its configuration without restarting.

Understanding signals is essential for production work: graceful shutdowns, zero-downtime config reloads, and debugging hung processes all involve signals.

Signal delivery flow:

◈ DIAGRAM
Process A Kernel Process B (PID 1234)
kill -TERM 1234 | |
| | |
+-- sys_kill() -------> | |
| |
+-- mark SIGTERM -------> |
pending for 1234 |
|
Next scheduler tick: |
check pending signals |
| |
+-- has SIGTERM handler?
|
yes: run handler + clean up
no: default action (terminate)

How It Works

The kernel delivers signals to a process by setting a flag in the process descriptor. The next time the process is scheduled to run, the kernel checks this flag and calls the signal handler (or default action) before resuming normal execution.

◈ DIAGRAM
Process A (your shell)
kill -TERM 1234
|
v
Kernel: mark signal SIGTERM pending for PID 1234
|
v
Next time PID 1234 runs, kernel checks pending signals
|
v
PID 1234 has a SIGTERM handler -> handler runs
Handler does: close connections, flush buffers, exit cleanly

The most important signals:

TEXT
SIGHUP (1) Hang up / reload
Traditional meaning: terminal disconnected
Modern use: reload configuration without restart
Example: nginx reloads nginx.conf on SIGHUP
SIGINT (2) Interrupt (Ctrl+C)
Polite request to stop
Default: terminate the process
Can be caught and handled
SIGKILL (9) Kill immediately
Cannot be caught, blocked, or ignored
Kernel terminates the process immediately
No cleanup, no graceful shutdown
Use as last resort only
SIGTERM (15) Terminate
Polite request to terminate
Default: terminate the process
Can be caught -- process can clean up first
This is what `kill PID` sends by default
SIGSTOP (19) Pause execution
Cannot be caught or ignored
Process is frozen until SIGCONT
SIGCONT (18) Continue after stop
Resume a SIGSTOP'd process
SIGUSR1 (10) User-defined signal 1
SIGUSR2 (12) User-defined signal 2
Application-specific meaning
Many apps use USR1 for log rotation
SIGCHLD (17) Child process changed state
Sent to parent when child exits
Parent must wait() to reap the child

Why SIGKILL should be a last resort:

TEXT
SIGTERM allows the process to:
- Finish processing the current request
- Close database connections properly
- Flush write buffers to disk
- Remove lock files and PID files
- Log the shutdown event
SIGKILL gives none of these opportunities:
- In-flight requests are dropped
- Database connections abruptly closed (may leave locks)
- Write buffers lost (potential data corruption)
- Lock files remain (next start may fail)
Production shutdown sequence:
1. Send SIGTERM
2. Wait 30 seconds for graceful shutdown
3. If still running, send SIGKILL

Practical Commands

Bash
## Send SIGTERM (default) to a process
kill 1234
kill -15 1234 ## same
kill -TERM 1234 ## same
## Send SIGKILL
kill -9 1234
kill -KILL 1234 ## same
## Reload config (SIGHUP)
kill -HUP 1234
kill -1 1234 ## same
## Kill by name (all processes with that name)
killall nginx
pkill nginx
## Kill by pattern
pkill -f 'python3 worker.py'
## Check if a process is alive (signal 0 = test only, no signal sent)
kill -0 1234
## exit code 0 = process exists
## exit code 1 = process does not exist or no permission
## List all signal names and numbers
kill -l
## Send SIGUSR1 to trigger log rotation in many daemons
kill -USR1 $(cat /var/run/nginx.pid)
## Trap signals in bash scripts
trap 'echo "Caught SIGTERM, cleaning up..."; rm -f /tmp/mylock; exit 0' TERM
trap 'echo "Caught SIGINT, cleaning up..."; rm -f /tmp/mylock; exit 1' INT
## Or trap EXIT to run cleanup no matter how the script ends
tmpfile=$(mktemp)
trap 'rm -f "$tmpfile"' EXIT

Troubleshooting

Symptom Command What to Look For
kill not working kill -0 PID Process in D state -- cannot kill
Service not reloading on SIGHUP Check app docs App may not implement SIGHUP handler
Process ignoring SIGTERM Send SIGKILL after timeout Process catching and ignoring SIGTERM
Zombie processes `ps aux grep Z`
Tip

kill -0 PID is the safest way to check if a process is running. It sends no actual signal — it just checks if the process exists and you have permission to signal it. Exit code 0 means the process is alive. This is useful in scripts that need to check process health without affecting it.

Remember

SIGKILL (9) cannot be caught, blocked, or ignored — it is implemented entirely in the kernel, not in the process. This is both its power and its danger. It always works, but it gives the process no opportunity to clean up. Always try SIGTERM first and wait a reasonable time before escalating to SIGKILL.

Security

Any process running as the same user can send signals to any other process of that user. Root can signal any process. This is why running services as dedicated service accounts with minimal permissions matters — a compromised service cannot send SIGKILL to other services running as different users.

Frequently Asked Questions

What's the practical difference between SIGTERM and SIGKILL that matters for deployments?

SIGTERM (signal 15) asks a process to shut down gracefully and can be caught, letting the app finish in-flight requests and close connections cleanly; SIGKILL (signal 9) forces immediate termination and cannot be caught, handled, or ignored by the process at all. Kubernetes sends SIGTERM when terminating a pod, waits out `terminationGracePeriodSeconds` (default 30s), and only sends SIGKILL if the process is still running after that window.

What's a common mistake with signal handling in containerized apps?

Running your app as PID 1 in a container without a proper init process means it may not receive or correctly forward signals — some language runtimes and shell wrappers swallow SIGTERM instead of passing it to the actual application, so Kubernetes' graceful shutdown never happens and every pod termination hits the hard SIGKILL timeout. Using `tini` or a runtime's built-in signal forwarding (or `exec` instead of shell-form CMD in a Dockerfile) avoids this.