POSIX Signals - SIGPIPE
SIGPIPE (Broken Pipe, signal number 13) is a standard POSIX signal sent by the operating system kernel to a process when it attempts to write data to a pipe, FIFO, or network socket whose reading end has been closed.
Because the default action for SIGPIPE is immediate, silent process termination, an unhandled SIGPIPE is one of the most common reasons backend web servers, reverse proxies, and microservices crash in production when client browsers abruptly close connections.
How SIGPIPE Is Generated
┌────────────────────────────────────────────────────────┐
│ Process A (Writer) │
│ Calls write(fd, buf, len) or send(fd, buf, len, 0) │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ Operating System Kernel │
│ Checks read-end reference count of pipe or socket state│
└──────────────────────────┬─────────────────────────────┘
│
┌─────────────┴─────────────┐
│ Reader still open? │ Reader closed / RST received?
▼ ▼
┌──────────────────────────┐┌────────────────────────────┐
│ Data queued to buffer ││ 1. Kernel raises SIGPIPE │
│ write() returns bytes ││ 2. write() returns -1 │
│ ││ with errno == EPIPE │
└──────────────────────────┘└────────────┬───────────────┘
│
▼
[ Is SIGPIPE Handled/Ignored? ]
│
┌───────────────┴───────────────┐
│ Default: Terminate Process │ Ignored (SIG_IGN):
▼ ▼
[ PROCESS DIES ] [ Program handles EPIPE ]
The Two Common Trigger Scenarios
- Pipes and Shell Pipelines:
In a pipeline like
cat huge_file.txt | head -n 5,headreads 5 lines and exits, closing standard input. Whencatsubsequently attempts to write the next chunk of data to the closed pipe, the kernel sendsSIGPIPEtocat, terminating it cleanly so it stops wasting CPU cycles. - Network Sockets (The Server Crasher):
In a TCP server, a client opens a connection, requests a large response (e.g. streaming a 50MB file), and the user closes their browser tab or loses Wi-Fi connection. The client operating system sends a TCP
RST(Reset) packet. When the server callswrite()orsend()on the socket, the kernel deliversSIGPIPE, instantly killing the backend server if unhandled.
Three Defenses Against SIGPIPE
Production software should never allow SIGPIPE to terminate a server. There are three standard methods to neutralize it:
Method 1: Globally Ignore SIGPIPE (The Industry Standard)
The most robust approach in network daemons is to ignore SIGPIPE at process startup:
#include <signal.h>
void setup_signals(void) {
struct sigaction sa;
sa.sa_handler = SIG_IGN; // Ignore signal
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGPIPE, &sa, NULL);
}
When SIGPIPE is ignored, any subsequent write() or send() to a broken connection simply returns -1 with errno == EPIPE:
ssize_t bytes_sent = write(client_fd, buffer, length);
if (bytes_sent == -1) {
if (errno == EPIPE || errno == ECONNRESET) {
// Client disconnected gracefully; close socket and free resources
close(client_fd);
} else {
perror("write error");
}
}
Method 2: Per-Call MSG_NOSIGNAL Flag
On Linux and POSIX-compliant systems, you can suppress SIGPIPE on individual socket calls using the MSG_NOSIGNAL flag in send():
#include <sys/socket.h>
// Suppresses SIGPIPE exclusively for this transmission
ssize_t sent = send(client_fd, data, len, MSG_NOSIGNAL);
if (sent == -1 && errno == EPIPE) {
// Handled locally without generating a signal
}
Method 3: SO_NOSIGPIPE Socket Option (macOS & BSD)
On Apple macOS and FreeBSD systems where MSG_NOSIGNAL was historically unavailable, disable SIGPIPE per-socket via setsockopt:
int optval = 1;
setsockopt(client_fd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval));
Comparison of Defenses
| Technique | Scope | Portability | Thread Safety |
|---|---|---|---|
sigaction(SIGPIPE, SIG_IGN) |
Entire Process | Universal POSIX (Linux, macOS, BSD, Solaris) | Thread-safe (process-wide mask) |
send(..., MSG_NOSIGNAL) |
Single Syscall | Linux, modern POSIX (sockets only; not pipes) | Fully thread-safe |
setsockopt(..., SO_NOSIGPIPE) |
Single Socket | macOS, iOS, FreeBSD (not available on Linux) | Fully thread-safe |
Frequently Asked Questions (FAQ)
What is the default action when a process receives SIGPIPE?
The default action for SIGPIPE is abnormal process termination without producing a core dump. If an application does not explicitly ignore SIGPIPE or install a custom signal handler, any write to a closed socket or pipe causes the operating system to kill the process immediately.
What error does write() return when SIGPIPE is ignored?
When SIGPIPE is ignored (SIG_IGN) or blocked, write() and send() return -1 and set errno to EPIPE (Broken pipe). This allows software to gracefully clean up client connections and log disconnect events rather than crashing.
Why does SIGPIPE exist if write() can already return EPIPE?
SIGPIPE was introduced in early Unix to terminate upstream processes in command-line shell pipelines (like yes | head -n 1) as soon as the downstream consumer exits. Without SIGPIPE, infinite generator programs like yes would continue executing and spinning the CPU forever if they did not explicitly check the return value of every write operation.
Related Topics
- Socket I/O Syscalls (
send,recv,sendmsg): Detailed flag semantics includingMSG_NOSIGNAL. - POSIX Signals Architecture & sigaction: Signal delivery, masks, and async-signal safety.
- POSIX Process Groups & Sessions: Pipeline job control and signal routing.
