What Triggers SIGHUP?
SIGHUP (signal number 1) is the “Hangup” signal. It was originally designed for modem-based terminals — when the phone line dropped (hung up), the terminal driver notified the process.
Today, SIGHUP is triggered in two situations:
- Modem/serial line disconnect: The terminal driver detects a loss of signal on a modem or terminal line.
- Terminal window closed: When you close a terminal window (xterm, gnome-terminal, etc.), the last file descriptor for the master side of the pseudoterminal is closed. This causes a disconnect event.
In both cases, the kernel sends SIGHUP to the controlling process (usually the login shell).
What Happens When the Controlling Process Receives SIGHUP?
The default action of SIGHUP is to terminate the process. If the controlling process (shell) terminates, a chain reaction begins.
The chain reaction happens in two ways:
Most shells handle SIGHUP and, before terminating, send SIGHUP to each process group they created (both foreground and background). This kills those jobs by default, or notifies them if they have a SIGHUP handler.
When the controlling process terminates (for any reason, not just SIGHUP), the kernel:
(a) Disassociates all session processes from the controlling terminal
(b) Frees the terminal for another session
(c) Sends SIGHUP (+ SIGCONT on Linux) to all members of the terminal’s foreground process group
| Terminal Window Closed | ||
| ↓ | ||
| SIGHUP sent to Controlling Process (bash) | ||
| ↓ | ↓ | |
| Shell forwards SIGHUP to its background jobs |
Kernel sends SIGHUP to foreground group |
|
SIGHUP After the Controlling Process Terminates — Read() Behaviour
If the controlling process handles or ignores SIGHUP (does not terminate), then any subsequent read() from the terminal by other processes will return end-of-file.
Special Use: Daemon Reinitialisation
Example: kill -HUP $(cat /var/run/nginx.pid) — this tells nginx to reload its config without stopping.
Code Example 1 — Catching and Logging SIGHUP
/* sighup_demo.c
* Simple program that catches SIGHUP and logs it.
* Run in background, then close the terminal to observe SIGHUP.
* Compile: gcc -o sighup_demo sighup_demo.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <string.h>
static volatile int sighup_count = 0;
static void sighup_handler(int sig)
{
/* Signal-safe: only increment a counter here.
* Do real work in the main loop. */
sighup_count++;
}
int main(void)
{
/* Install SIGHUP handler */
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = sighup_handler;
if (sigaction(SIGHUP, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
printf("PID=%ld PGID=%ld SID=%ld\n",
(long)getpid(), (long)getpgrp(), (long)getsid(0));
printf("Waiting for SIGHUP (close terminal or: kill -HUP %ld)\n",
(long)getpid());
fflush(stdout);
int prev_count = 0;
while (1) {
sleep(1);
if (sighup_count != prev_count) {
/* Real work done here, not in handler */
printf("Received SIGHUP (total count: %d) — reinitialising...\n",
sighup_count);
fflush(stdout);
/*
* In a real daemon, this is where you would:
* - reread config files
* - reopen log files
* - reset state
*/
prev_count = sighup_count;
}
}
return 0;
}
/* Send SIGHUP manually: kill -HUP <PID>
* Output: Received SIGHUP (total count: 1) — reinitialising...
* This is exactly how nginx, sshd, etc. reload their configs.
*/
Code Example 2 — nohup() Effect: Ignoring SIGHUP
/* nohup_demo.c
* Demonstrates the effect of ignoring SIGHUP (like nohup(1) does).
* Compile: gcc -o nohup_demo nohup_demo.c
* Normal run: ./nohup_demo ← will die on terminal close
* Nohup run: nohup ./nohup_demo & ← survives terminal close
*
* This code manually does what nohup(1) does for you.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
int main(int argc, char *argv[])
{
/* If --immune flag is given, ignore SIGHUP (like nohup) */
if (argc > 1 && strcmp(argv[1], "--immune") == 0) {
if (signal(SIGHUP, SIG_IGN) == SIG_ERR) {
perror("signal");
return 1;
}
printf("SIGHUP is IGNORED — this process is immune to terminal close\n");
} else {
printf("SIGHUP has DEFAULT action — this process will die on terminal close\n");
}
printf("PID=%ld running... close terminal or: kill -HUP %ld\n",
(long)getpid(), (long)getpid());
fflush(stdout);
/* Stay alive and periodically write to a log file */
FILE *log = fopen("/tmp/nohup_demo.log", "a");
if (!log) log = stderr;
for (int i = 0; i < 60; i++) {
sleep(1);
fprintf(log, "tick %d (PID=%ld)\n", i, (long)getpid());
fflush(log);
}
if (log != stderr) fclose(log);
return 0;
}
/* Test:
* ./nohup_demo --immune &
* close terminal window
* check /tmp/nohup_demo.log — process kept running!
*/
