Intro
Unix signals are one of the oldest forms of inter-process communication. Born in early UNIX at Bell Labs (1970s), they were designed as a simple way for the kernel to tap a process on the shoulder. Unlike pipes or sockets, signals don’t carry payloads — just a notification.
They remain the universal way the OS tells programs when it’s time to stop, reload, or respond to user actions. Servers, daemons, shells, even containers all depend on them. Think of signals as lifecycle control, while other IPC mechanisms handle the heavy lifting.
Fun Fact: The very first “signals” were just conventions that most but not all programs hard-wired for job control (Ctrl+C, Ctrl+Z). Everything else—portability, universality, reloads, graceful quits—are clever conventions layered on later.

Still from WarGames (1983, MGM). Used under fair use for educational purposes.
Unix IPC signals are easy to use but there are a lot of exceptions and footnotes. I tried to make the following as clear as possible without going full-on manual.
Signals vs other Unix-land IPC
The more they over-think the plumbing the easier it is to stop up the drain. –anonymous fortune
-
Signals are almost all one-bit notifications from the kernel. They don’t carry payloads, just a type (e.g.
SIGTERM
), and they interrupt a process asynchronously. The exception being POSIX real-time signals likesigqueue
which can carry a small int/pointer. Use them for lifecycle events (quit, reload, stop) and simple control. -
Pipes (
man 7 pipe
) are byte streams between processes, usually set up with|
in the shell. Data flows in one direction, buffered by the kernel (half-duplex). Bidirectional requires two pipes. Think ofls | grep foo
— that’s a pipe doing work. Pipes are synchronous: the reader blocks until there’s data. There are also named pipes or FIFOs, made withmkfifo
, which persist in the filesystem and let unrelated processes talk. Same half-duplex rule applies. -
Sockets (
man 7 socket
) generalize pipes into a full-duplex/bidirectional, network-style interface. They can talk locally (UNIX domain sockets) or across machines (TCP/UDP). Most client-server software is built on sockets. -
Message Queues & Shared Memory (
man 7 mq_overview
,man 7 shm_overview
) are heavier IPC mechanisms. They allow structured data passing or even memory regions mapped between processes. Used when performance matters or when large state must be shared. -
DBus (freedesktop.org) is a high-level message bus, running in user space via a broker daemon. Think of it as an application-layer IPC system: processes send structured requests and events through a central hub. Desktop environments and system services (NetworkManager, systemd, GNOME) rely on it. DBus “signals” are logical user-space events, not kernel ones.
Pro-Tip: If you’re just controlling a process, signals are usually enough. If you need to talk to it, look at sockets or DBus.
Usage Cheatsheet
Common signals you’ll run into:
Signal | Trigger / Use case | Default action | Notes |
---|---|---|---|
SIGINT |
Ctrl+C in a terminal | Terminate | Apps can catch to clean up. |
SIGTERM |
kill <pid> or systemctl stop |
Terminate | The polite way to ask a process to quit. |
SIGHUP |
Terminal hangup; config reloads | Terminate | Many daemons repurpose it as “reload config.” |
SIGQUIT |
Ctrl+\ | Core dump + exit | some like nginx use it for graceful quit. |
SIGKILL |
kill -9 <pid> |
Terminate (uncatchable) | No cleanup, can’t be trapped. |
SIGTSTP |
Ctrl+Z | Stop (suspend) | Background job control; fg to resume. |
You can list them all with kill -l
and you will get a list like this:
└─$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
Note: the list and numbers shown are from Linux. Signal names are portable, but numbers and availability differ on BSD/macOS/Solaris.
Most of the time you only care about the common signals, and modern tools let you use their names (SIGTERM
, SIGHUP
, etc.) directly.
The old habit of using numbers (kill -9
or kill -1
) still works, but it’s less portable since signal numbers vary across Unix flavors. Stick to names unless you’re typing a quick shortcut.
The main tool for sending signals is the kill
command which despite its name, it doesn’t always “kill” a process. By default it throws SIGTERM
, but you can specify any signal. Think of it as the userland signal throwing tool that just defaults to kill:
# First, find the PID of the process you want to affect
ps aux | grep nginx
# Then send it a signal
kill -TERM <pid> # send SIGTERM (default)
kill -HUP <pid> # send SIGHUP
kill -KILL <pid> # send SIGKILL (can’t be trapped)
killall -HUP nginx # send SIGHUP to all nginx processes
Pro-Tip: With kill -9 <pid>
you must supply the PID of every process you want to kill. That’s why tools like pkill
and killall
are so handy.
Beyond kill
: other ways to signal processes
Signals aren’t just for kill
. The shell and related tools let you affect processes in different ways:
-
Job Control (in the shell)
-
Ctrl+Z
→ sendsSIGTSTP
(suspend) via the terminal driver. -
bg
→ resumes a job in the background (sendsSIGCONT
). -
fg
→ brings a job back to the foreground. -
jobs -l
→ shows current job table with PIDs.# Start a long-running process in the foreground sleep 600 # Press Ctrl+Z in the terminal # Output will look like: [1]+ Stopped sleep 600 # See it in the job table (with PID) jobs -l [1]+ 12345 Stopped sleep 600 # Resume it in the background bg %1 [1]+ 12345 Running sleep 600 & # Bring it back to the foreground fg %1 sleep 600
bg
andfg
only work on processes started from the current shell.- If the process expects input, running it in the background (
bg
) won’t magically make it non-interactive — it may still block waiting for input. - Background jobs still write to the terminal by default. Use output redirection (
> file 2>&1
) if you don’t want them spamming your shell. - If you close the terminal, jobs will get
SIGHUP
unless you’ve usednohup
ordisown
. - In
bash
/zsh
you can reference jobs by number%1
,%2
, or by name substring:fg %sleep
.
-
-
nohup
Run a command that keeps going even after you log out of the shell:nohup hugo server -D &
This ignores
Notes:SIGHUP
, so the process keeps running if you log out of your shell or disconnect an SSH session. Output (both stdout and stderr) gets redirected tonohup.out
by default unless you specify otherwise.- Logging out: the process keeps running in the background.
- Logging back in (or SSH-ing again): the process is still alive, but it won’t show up in your
jobs
list since that’s tied to the original shell. Useps aux | grep hugo
orpgrep hugo
to find it. - Reboot: nohup does not survive a reboot. For persistence across restarts, use a service manager like
systemd
,supervisord
, or Docker. - Good use case: quick, long-running tasks during a session (like
nohup hugo server -D &
while you hack on a blog). Not a replacement for proper service management.
-
disown (bash/zsh only)
Removes a job from the shell’s job table so it won’t receiveSIGHUP
. The process keeps running, but the shell forgets about it.sleep 600 & jobs -l disown %1 jobs -l # now the job no longer shows up
Since it’s a shell builtin, there’s no
man disown
. Use:help disown # bash
or in zsh:
man zshbuiltins | less +/disown
-
renice
Adjust scheduling priority (not a signal, but related process control).- Lower nice value = higher CPU priority (use for important processes).
- Higher nice value = lower CPU priority (good for heavy background tasks).
- Niceness ranges from -20 (highest priority) to 19 (lowest priority). Default is 0
renice -n 19 -p <pid> # very "nice" → gets less CPU time renice -n 0 -p <pid> # reset to normal priority renice -n -5 -p <pid> # increase priority (root only)
-
killall / pkill
Send signals by process name or regex pattern, instead of hunting PIDs manually:kill -USR1 <pid> # send a specific signal to a given PID (works on one or a list of PIDs) kill -HUP 1234 1235 # send HUP to multiple PIDs killall -TERM vim # send TERM to all vim processes pkill -HUP nginx # send HUP to all processes named nginx pkill -HUP -f 'nginx.*worker' # send SIGHUP to any process whose command matches the regex "nginx.*worker" pkill -TERM -f 'jupyter-notebook' # restart all Jupyter notebook servers (if multiple users)
-
systemctl
Modern services often live undersystemd
. Commands likesystemctl stop
,reload
, andrestart
rely on signals under the hood, but with extra policy:stop
→ sends SIGTERM, then escalates to SIGKILL if the process doesn’t exit withinTimeoutStopSec=
.reload
→ runsExecReload=
from the unit file, or if unset, sends the service’sReloadSignal=
(often SIGHUP).restart
→ does a stop, then start (so SIGTERM → SIGKILL if needed, then new process).
Pro-Tip:
Backgrounding (&
, bg
) isn’t just convenience — it literally controls signals (SIGSTOP
, SIGCONT
) at the kernel level.

Bash Traps
A common pattern is to trap several signals at once, so your script can clean up or log before exiting.
Here’s an example script that spins up a background worker, and makes sure it’s killed cleanly on exit or interrupt:
#!/usr/bin/env bash
# demo-trap.sh
# Start a dummy background job
sleep 600 &
worker_pid=$!
echo "Worker started with PID $worker_pid"
# Define cleanup function
cleanup() {
echo "Caught signal, cleaning up..."
kill -TERM "$worker_pid" 2>/dev/null
wait "$worker_pid" 2>/dev/null
echo "Worker stopped. Exiting."
exit 0
}
# Trap multiple signals
trap cleanup INT TERM HUP
# Main loop
while true; do
echo "Main script running. Press Ctrl+C to stop."
sleep 10
done
INT
→ user pressed Ctrl+CTERM
→ process was asked to stop politely (kill <pid>
orsystemctl stop
)HUP
→ hangup (often used as “reload”)
Pro-Tip: You can trap multiple signals in one line. Here, trap cleanup INT TERM HUP
ensures your cleanup runs whether you press Ctrl+C, close the terminal, or stop it from another shell.
Python Signal Handling
Profanity is the one language all programmers know best. –anonymous fortune
Python has the signal
module, which lets you trap signals much like Bash.
Typical use case: catch SIGINT
(Ctrl+C) or SIGTERM
(kill) to shut down cleanly.
Example:
#!/usr/bin/env python3
import signal
import sys
import time
# Define handler function
def handle_signal(signum, frame):
print(f"Caught signal {signum}, cleaning up...")
# do cleanup here
sys.exit(0)
# Register handlers
signal.signal(signal.SIGINT, handle_signal) # Ctrl+C
signal.signal(signal.SIGTERM, handle_signal) # kill <pid>
print("Running. Press Ctrl+C or send SIGTERM to stop.")
# Simulate work
while True:
time.sleep(1)
SIGINT
→ user pressed Ctrl+CSIGTERM
→ process asked to stop politely (kill <pid>
)
- Not every signal is catchable in Python. For example,
SIGKILL
andSIGSTOP
can never be trapped — the kernel enforces those. - Only the main thread can set handlers, and signals are always delivered to that thread.
- On Windows, only a limited set of signals work (
SIGINT
,SIGBREAK
, and a fakeSIGTERM
). - In async code, you can use
loop.add_signal_handler(signal.SIGTERM, callback)
to integrate withasyncio
.
Links and Stuff
Overview topics
- Signal (IPC)
- Inter-process communication
signal(7)
— overview of signalssignal-safety(7)
— what’s safe inside handlers
References
kill(1)
— man7 — manual page for thekill
commandpgrep(1)
/pkill(1)
— man7 — search or signal processes by name/regexkillall(1)
— man7 — signal all processes matching a name (note portability differences)- Linux kernel
signal.c
— kernel source handling signals & job control - Python
signal
module docs — official Python reference systemd.service(5)
— details onExecReload=
andReloadSignal=
in systemd units- POSIX
signal.h
— portable signal definitions
Conclusion
That’s it — a quick and dirty guide to signals in Unix-land.
Signals are one of those Unix fundamentals that everybody bumps into, but they can still trip you up if you don’t know the details.
Next time you press Ctrl+C
, background a process, or reload a daemon, remember: you’re working with a mechanism that’s been around since the 1970s and is still quietly running the show today.
📬 Got a favorite signal trick or a war story about a kill -9
gone wrong? Send it my way: feedback@adminjitsu.com