Signal: Explanation & Insights

A short notification the kernel sends to a running process to tell it to stop, pause, reload, or handle an event.

What It Is

A signal is a short, asynchronous notification the kernel delivers to a running process to tell it that something happened. That's the whole of it: a signal carries no data, no message body, no argument — just its own identity, a number from a small fixed set. It's the difference between a letter and a tap on the shoulder. The letter is a pipe or a socket, where one program sends another actual bytes to read. The signal is the tap: it doesn't say anything, it just makes you turn around. Which signal it is — that's the entire content. Number 2 means "the user pressed Ctrl-C." Number 15 means "please wind down and exit." Number 9 means "you're done, right now, no discussion."

"Asynchronous" is the word doing the heavy lifting. A process can be in the middle of anything — adding two numbers, halfway through writing a file, blocked waiting on the network — and a signal can arrive at that instant. The kernel pauses whatever the process was doing, runs a small piece of code in response, and (usually) lets it pick up where it left off. The program didn't ask to be interrupted and doesn't get to choose when. Something out in the world happened — you pressed a key, a timer expired, the process tried to touch memory that wasn't its own — and the kernel reached in and tapped it on the shoulder.

Every signal has a name and a number. The names start with SIG and read like a list of things that can go wrong: SIGINT (interrupt), SIGTERM (terminate), SIGKILL (kill), SIGSEGV (segmentation violation), SIGHUP (hang-up). The numbers are what actually travel through the kernel; the names are for humans. When you type kill -9, the 9 is the signal — SIGKILL is just the friendly label for it. Confusingly, the kill command isn't really about killing at all; it's the general tool for sending any signal, and it got its blunt name because the most common thing you do with a signal is end a process. More on that on its own page.

Why It Matters

Signals are how the outside world steers a program that is otherwise minding its own business. Every time you press Ctrl-C and a runaway command stops, that's a signal. Every time you kill a stuck server, that's a signal. Every time you run kill -HUP on nginx and it re-reads its config without dropping a single connection, that's a signal — and a particularly clever reuse of one, which we'll come back to, because the story behind it is better than you'd guess.

For anyone running a server, signals are the vocabulary of shutting things down gracefully. There's a world of difference between asking a database to flush its buffers and close its files cleanly, and yanking the power cord — and that difference is the difference between two signals. Get it wrong and you corrupt data; get it right and the process tidies up after itself like a guest who does the dishes before leaving. Knowing which signal does which is most of knowing how to stop a program without breaking it.

How a Signal Travels

Here's the journey, start to finish, because every piece of signal trouble lives somewhere on this path.

Something decides a process should be notified. That something is almost always the kernel itself — a timer fired, a child process died, the process committed a memory crime — or another process calling kill (which, under the hood, asks the kernel to do the actual delivering; one process can't reach into another, only the kernel can). Either way, the kernel marks the signal as pending for the target process: a single bit flips in a little bitmap the kernel keeps for every process, one bit per signal. The signal now exists, but nothing has happened yet — it's a note in the process's inbox, unread.

The process doesn't get interrupted the very instant the bit flips. The kernel waits for a natural moment — typically when the process is about to return from the kernel back into its own code, which happens constantly. At that moment the kernel checks the pending bitmap, and if a signal is waiting, it delivers it. Delivery is where the action is, and the process gets a say in what "delivery" means. For each signal, the process has registered one of three responses in what's called its signal disposition — think of it as a small table, one row per signal, that says "when this one arrives, do that":

  • Default. The process never said otherwise, so the kernel does the built-in thing for that signal. For most signals the default is to kill the process. For a few it's to stop it, ignore it, or dump core (write the process's memory to a file for debugging).
  • Ignore. The process asked the kernel to throw this signal away on arrival. The bit still flips, the signal is still pending, but delivery does nothing.
  • Catch. The process registered a function — a signal handler — and the kernel runs it. This is the interesting one: the process's normal flow freezes, the handler runs, and then normal flow resumes exactly where it was. The handler might log something, set a flag, reload a config, then return as if nothing happened.

That last option is the whole reason graceful shutdown exists. A well-built server catches SIGTERM, and its handler doesn't just exit — it stops accepting new work, finishes what's in flight, flushes its buffers, closes its files, then exits. The signal asked it to leave; the handler made it leave like a professional.

The Two That Can't Be Caught

Almost every signal can be caught or ignored. Two cannot: SIGKILL (9) and SIGSTOP (19). There is no handler you can register for them, no way to ignore them, no way to block them. The kernel does not give the process a vote.

This is deliberate, and it's the safety valve that makes the whole system governable. If every signal could be caught, a misbehaving — or malicious — program could simply register a handler that does nothing for every signal, and become genuinely immortal: unstoppable, unkillable, ignoring every request to leave. So the designers carved out two that bypass the process entirely and are handled by the kernel alone. SIGKILL ends the process immediately, full stop — no cleanup, no handler, no goodbye. SIGSTOP freezes it mid-stride, like pausing a film; it sits there using no CPU until a SIGCONT (18) tells the kernel to press play again.

Note

Because SIGKILL skips the process entirely, the program never gets to clean up — open files aren't flushed, locks aren't released, half-written data stays half-written. That's why kill -9 is the last resort, not the first. Always try SIGTERM (the plain kill) and give the process a moment before reaching for 9.

There's a quiet joke in this pairing once you sit with it. SIGKILL and SIGSTOP are the two no process gets a vote on — but they're opposites. One is the end, the other is a pause. You can ask a program to please shut down and it can mull it over; you cannot ask it to please be stopped. That decision was made above its pay grade.

And even SIGKILL has a limit, which surprises people: a process stuck in an uninterruptible sleep — waiting on hardware that may never answer, the D state — won't die even from kill -9, because it's not in a place where the kernel can deliver anything to it at all. It isn't ignoring the signal; it simply isn't home to receive it. That whole story belongs to the process page, so I'll leave it there.

The Signals You'll Actually Meet

You don't need all sixty-odd. A handful carry almost all the weight on a real server.

SIGTERM (15) — terminate. The polite request: "please shut down." It can be caught, so a well-behaved program gets to clean up first. This is what plain kill <pid> sends, and what your init system sends when you stop a service. Always your first move.

SIGKILL (9) — kill. The unblockable one above. Not a request — an execution. Use only when SIGTERM was ignored.

SIGINT (2) — interrupt. What Ctrl-C sends to whatever's running in your terminal. "Stop, I changed my mind." Catchable — which is why a program can ask "Really quit? (y/n)" instead of just dying.

SIGHUP (1) — hang-up. The one with the best backstory, below. On a server today it almost always means "re-read your config without restarting."

SIGQUIT (3) — quit. Like SIGINT but ruder: it also dumps core for debugging. Ctrl-\ sends it.

SIGSEGV (11) — segmentation violation. The kernel sends this to a process that touched memory it doesn't own — the classic crash. Default action: die, and leave a core dump. If you've ever seen "Segmentation fault," that was SIGSEGV landing.

SIGCHLD (17) — child changed state. Sent to a parent process when one of its children dies. This is the kernel tapping the parent on the shoulder to say "your kid is done — come collect them." Ignore it and you grow zombie processes; the parent is supposed to catch it and reap the child (acknowledge the death so the kernel can free the slot). The reaping story lives on the process page.

SIGSTOP (19) / SIGCONT (18) / SIGTSTP (20) — stop and continue. SIGTSTP is the catchable Ctrl-Z that suspends a job; SIGSTOP is the uncatchable version; SIGCONT resumes either. This trio is how shell job control freezes and thaws programs.

SIGPIPE (13) — broken pipe. Sent when a process writes to a pipe whose reader has already gone away — you piped into something, it quit, and now you're shouting into a hung-up phone. Default action is to die, which is exactly why yes | head exits cleanly instead of running forever.

SIGUSR1 (10) / SIGUSR2 (12) — user-defined. The kernel attaches no meaning to these two; they're left blank on purpose, for programs to define however they like. Many daemons use them for "rotate your logs now" or "dump your stats."

Pro Tip

When a service won't stop, resist the reflex to jump to kill -9. Send SIGTERM first (kill <pid>), wait a few seconds, and watch. Most well-built programs are tidying up in those seconds — flushing, closing, finishing. Reaching for 9 immediately throws that cleanup away for no reason and is how databases end up needing crash recovery on the next boot.

The Whole List

Run kill -l (that's a lowercase L, for "list") and the kernel hands back every signal name it knows, numbered:

 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

Two things to notice. There's a gap — number 31 jumps to 34, and there's no 32 or 33. Those are reserved by the threading library for its own private use and aren't shown. And the back half, 34 through 64, is a different breed entirely: the real-time signals, SIGRTMIN through SIGRTMAX. The classic signals above can't queue — if SIGUSR1 arrives twice before the process gets to it, the second one is simply lost, because it's just one bit and the bit is already set. The real-time signals fixed that: they queue up and arrive in order, and they have no fixed meanings, so applications use them when one tap on the shoulder per type isn't enough. Most admins never touch them; now you know what that long tail is.

SIGHUP, and the Ghost in the Machine

This is the one worth slowing down for, because the name makes no sense until you know where it came from — and then it makes perfect sense, and you can't unsee it.

SIGHUP stands for hang-up. Not "hang up" as in stuck — hang up as in a telephone. Go back to the 1970s, when you didn't sit at a computer; you sat at a terminal, often a teletype, connected to a distant machine over a phone line through a modem. When you were done, you hung up the phone. The modem dropped the carrier signal, and the kernel needed to tell every program you'd been running that the line had gone dead — your terminal had, quite literally, hung up. That notification was SIGHUP. Its default action is to kill the process, which is exactly right: if the human's connection just dropped, the programs they left running on the terminal should probably stop too.

That's a piece of dead hardware now. Hardly anyone hangs up a modem to end a session. But the signal didn't go away — it found a second life, and this is the part that gets me every time.

A daemon — a long-running background server like a web server — has no terminal. It was deliberately cut loose from any terminal when it started, precisely so that no one hanging up a phone could ever kill it. Which means a daemon can never receive SIGHUP for its original purpose. The signal that meant "your terminal died" is, for a daemon, completely orphaned — a notification about an event that can never happen to it. So daemon authors looked at this spare, meaningless-to-them signal and gave it a new job by convention: "when you get a SIGHUP, re-read your configuration files." Nothing in the kernel says SIGHUP means "reload." It's pure agreement among programmers, built on top of a signal that was lying around unused. To this day, kill -HUP $(pidof nginx) reloads nginx's config without dropping a connection — and the reason it's HUP and not some sensible "reload" signal is that someone, decades ago, had a free signal and a problem, and married the two. The phone line is long gone; the hang-up lives on, doing a job it was never built for. Whole layers of computing have come and gone, and we still send a "your modem disconnected" message to reload a web server.

Sending and Catching: How You Actually Use Them

To send a signal, you use kill, and you name the signal by number or by name:

kill 4823            # sends SIGTERM (15) by default — the polite ask
kill -TERM 4823      # the same thing, spelled out
kill -9 4823         # SIGKILL — the unblockable one, last resort
kill -HUP 4823       # "reload your config" by convention
kill -l              # list every signal name and number

You can also signal a whole group of processes by name with pkill (pkill -HUP nginx), or send SIGINT to the foreground job by just pressing Ctrl-C — the terminal turns the keystroke into a signal for you.

To catch a signal in a shell script, you use the built-in trap. This is how a script cleans up after itself even if someone Ctrl-C's it halfway through:

#!/bin/bash
tmpfile=$(mktemp)
trap 'rm -f "$tmpfile"; echo "cleaned up, exiting"; exit' INT TERM

# ... long-running work that uses $tmpfile ...

That trap line says: "if SIGINT (Ctrl-C) or SIGTERM arrives, run this cleanup instead of just dying." The temp file gets removed no matter how the script ends. It's the shell-script version of catching a signal in a handler, and it's the difference between a script that litters the disk with stray files and one that tidies up after itself.

Warning

You can trap SIGINT and SIGTERM, but you cannot trap SIGKILL (9) or SIGSTOP (19) — the kernel won't let you, by design. If your cleanup absolutely must run, never rely on it surviving a kill -9. There's no surviving a kill -9; that's the entire point of it.

See Also

  • kill — the command for sending any signal, not just for killing
  • pkill — send a signal to processes matched by name
  • trap — catch signals inside a shell script
  • ps — find the PID you want to signal
  • process — process states, the uninterruptible D state, zombies, and reaping
  • kernel — the thing that actually delivers every signal
  • nginx — the classic kill -HUP graceful reload in practice

Stuck process won't respond — and you're not sure which signal will actually move it?

CleverUptime watches for the processes that hang, spike, or refuse to die, and tells you plainly which one is wedged and what to send it.

Want to see your own server's health right now? One command, no signup, no install.

Check your server →