Innovate | Connect | Transform
ContactService statusUSD $

GICT knowledge base

Understanding Signals in Linux: SIGTERM, SIGKILL, and the Others

Published

Linux Administration
Understanding Signals in Linux: SIGTERM, SIGKILL, and the Others

Every process in Linux can be interrupted, paused, or terminated by signals. When you press Ctrl+C, when you run kill, when a process tries to write to a closed pipe — all of these are signals. Understanding signals is the difference between a service that shuts down cleanly and one that corrupts data on exit.

What signals actually are

A signal is a software interrupt sent to a process by the kernel, another process, or the terminal. It is a way of telling a running program that something happened and it should react.

When a signal arrives, the process has three choices. It can handle it with a custom handler you write in code. It can let the kernel use the default behavior. Or it can ignore it entirely, for signals that allow it.

The full list of signals is available with:

kill -l

Each signal has a name and a number. SIGTERM is signal 15. SIGKILL is signal 9. The number matters when you use kill, not the name.

The signals that matter most

SIGTERM (15) — Polite shutdown request

SIGTERM is the default signal sent when you run kill without specifying a signal. It asks a process to terminate gracefully. The process receives the signal, runs any cleanup handlers it has, closes open files and connections, and exits with code 143 (128 + 15).

A well-behaved service handles SIGTERM. When you run systemctl stop nginx, systemd sends SIGTERM and waits for nginx to exit on its own terms. This is the clean shutdown path.

SIGKILL (9) — Forced termination

SIGKILL cannot be caught, blocked, or ignored. When you send it, the kernel terminates the process immediately without giving it a chance to clean up. The process exits with code 137 (128 + 9).

Use SIGKILL when a process is hung and will not respond to SIGTERM, or when you need to force a service to stop and do not care about cleanup. There is no graceful shutdown with SIGKILL — any in-flight work is abandoned.

SIGINT (2) — Interrupt from keyboard

SIGINT is what Ctrl+C sends to the foreground process in your terminal. It is functionally equivalent to SIGTERM — the process is asked to stop. Many programs treat it as a clean shutdown.

Exit code for SIGINT is 130 (128 + 2).

SIGHUP (1) — Hang up

SIGHUP was originally used for telephone line drops — a modem hanging up. In modern Linux, it means the terminal session has ended. Daemon processes that have detached from the terminal use SIGHUP as a signal to reload their configuration without restarting.

When you close an SSH session while a process is running in the foreground, that process receives SIGHUP. By default, that kills it. Running a process with nohup or in screen detaches it from the terminal SIGHUP signal.

For daemons like Nginx and Apache, SIGHUP triggers a config reload. The process does not restart — it re-reads its configuration files and reopens log files without dropping connections.

SIGPIPE (13)

When a process writes to a pipe or socket that has been closed by the reading end, it receives SIGPIPE. Without handling this, the writing process dies with a broken pipe error.

Most programs handle SIGPIPE internally — they detect the closed connection and stop writing rather than exiting. The shell manages SIGPIPE for pipelines automatically so that a failure in the last command does not kill the entire pipeline.

SIGCHLD (17)

When a child process exits, the kernel sends SIGCHLD to its parent. By default, Linux ignores this — the child becomes a zombie until the parent calls wait. If the parent never calls wait, the zombie persists until the parent exits.

For long-running servers that fork child processes, properly handling SIGCHLD matters to prevent zombie processes from accumulating.

How processes receive and handle signals

When a signal arrives, the kernel interrupts the process and transfers control to the signal handler — a function in the program that decides what to do. If the program has not defined a custom handler, the kernel applies the default action.

Default actions are: terminate, terminate with a core dump, stop the process, or ignore. SIGKILL and SIGSTOP always terminate or stop — they cannot be replaced.

Most application code does not write signal handlers directly. Instead, frameworks handle them. When you press Ctrl+C in a Node.js server, Node catches SIGINT and initiates a graceful shutdown. When Nginx receives SIGTERM, it finishes processing active connections before exiting.

Writing a trap handler in a shell script

Shell scripts can use the trap command to define what happens when signals arrive. This is essential for cleanup scripts that must finish their work even when interrupted.

#!/bin/bash
trap "echo Caught SIGTERM, cleaning up...; rm /tmp/lockfile; exit 0" TERM

echo "Service started, PID: $$"

# Simulate a long-running service
while true; do
    sleep 1
done

When this script receives SIGTERM, it writes the cleanup message, removes the lock file, and exits cleanly. Without the trap, the script would terminate immediately and the lock file would stay on disk.

The trap command accepts the signal name without the SIG prefix. You can catch multiple signals with one handler:

trap "cleanup; exit" TERM INT HUP

The exit inside the handler is important — without it, the script continues running after handling the signal.

The signals that cannot be caught

SIGKILL and SIGSTOP are special. They go directly to the kernel’s control flow and bypass the process entirely. No signal handler runs. No cleanup happens. The process is paused or killed at the hardware level.

There is no graceful shutdown possible with SIGKILL. If a process is holding a lock, writing to a database, or sending data over a network, SIGKILL abandons that work. Only use it when SIGTERM has failed or when you are certain the process is hung and clean shutdown is not possible.

Real-world signal scenarios

Graceful vs forced shutdown

When a VPS reboots, systemd sends SIGTERM to all running services, waits a timeout, then sends SIGKILL to anything still running. This is why services that handle SIGTERM cleanly restart faster and lose fewer in-flight requests — systemd does not have to wait for the timeout to expire.

Terminal disconnect

If you SSH into a server and start a long-running script, closing the terminal sends SIGHUP to that process. Without nohup, the script exits. With nohup, the script continues running after you disconnect:

nohup ./long_task.sh &

Or use screen to keep the session alive:

screen -S mytask
./long_task.sh
# Press Ctrl+A, then D to detach

Broken pipe in pipelines

When you pipe output to a command that finishes before your script does:

./generate_data.sh | head -n 10

If generate_data.sh does not handle SIGPIPE, it exits with a broken pipe error. Most shell-built commands handle this automatically, but custom scripts should ignore SIGPIPE or handle it explicitly.

Sending signals from the command line

kill -TERM PID      # Polite shutdown request
kill -KILL PID     # Forced termination
kill -INT PID      # Interrupt (like Ctrl+C)
kill -HUP PID      # Hang up / reload config
kill -STOP PID     # Pause (suspend) a process
kill -CONT PID     # Resume a stopped process
kill -SIGNAL PID   # Send a specific signal by name

Send to all processes with a matching name:

killall -TERM nginx

Or use pkill for pattern matching:

pkill -TERM -f "python.*server"
Exit codes from signals. When a process is killed by a signal, its exit code is 128 plus the signal number. SIGTERM (15) gives exit code 143. SIGKILL (9) gives 137. SIGINT (2) gives 130. If your script exits with 137 and you did not write code to produce that, something sent it SIGKILL.

Related guides

Account services are unavailable until the production backend is configured.