POSIX Signals - SIGHUP

SIGHUP (Hangup, signal number 1) is the first signal defined by POSIX standards.

Originating from early multi-user Unix systems connected over physical telephone lines and dial-up modems, SIGHUP was sent by the terminal serial driver when the remote modem detected a lost carrier signal—meaning the user had literally "hung up" the phone.

Today, SIGHUP has two distinct personalities in modern systems programming:

  1. Interactive Terminal Disconnection: Delivered to processes when an SSH connection drops or a terminal emulator window closes.
  2. Daemon Configuration Reload: Repurposed across production server daemons (Nginx, PostgreSQL, systemd, Apache) as the universal signal for hot configuration reload and log file rotation without downtime.
┌────────────────────────────────────────────────────────┐
│ Interactive Context: SSH / Terminal Disconnection      │
│ User closes laptop or Wi-Fi drops                      │
│ 1. Kernel detects controlling terminal hangup          │
│ 2. Delivers SIGHUP to Session Leader                   │
│ 3. Session Leader broadcasts SIGHUP to process groups  │
│ 4. Default Action: Terminate process                   │
└────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────┐
│ Server Context: Configuration Hot-Reload (Nginx, etc.) │
│ Administrator runs: kill -HUP $(cat /var/run/nginx.pid)│
│ 1. Daemon catches SIGHUP via sigaction                 │
│ 2. Re-parses configuration files on disk               │
│ 3. Re-opens rotated log files (/var/log/nginx/...)     │
│ 4. Spawns new worker pool with updated config          │
│ 5. Gracefully drains connections on old workers        │
└────────────────────────────────────────────────────────┘

1. Terminal Disconnection and Job Survivability

When an interactive terminal session terminates:

  • The kernel delivers SIGHUP to the Session Leader (usually your shell, e.g. bash or zsh).
  • Upon receiving SIGHUP, the shell automatically transmits SIGHUP to every active child process and background job running in that session.
  • Because the default action for SIGHUP is process termination, all background tasks abruptly die unless protected.

Protecting Jobs with nohup and disown

# 1. Using nohup (POSIX utility):
# Sets SIGHUP disposition to SIG_IGN and redirects stdout/stderr to nohup.out
nohup ./long_running_job.sh &

# 2. Using disown (Shell built-in):
# Removes running job from shell's internal tracking table
./long_running_job.sh &
disown %1

2. The Daemon Pattern: Hot Configuration Reload

Because long-running Unix daemons detach from their controlling terminals during startup (using setsid()), they are completely immune to terminal hangups.

Software architects repurposed SIGHUP as the standard administrative control signal to request an in-memory configuration reload without terminating the service or dropping active client TCP connections:

C Implementation Pattern

#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>

volatile sig_atomic_t g_reload_config = 0;

void handle_sighup(int signum) {
    // Only set an atomic flag; do NOT parse files inside the signal handler!
    g_reload_config = 1;
}

int main() {
    struct sigaction sa = {
        .sa_handler = handle_sighup,
        .sa_flags   = SA_RESTART,
    };
    sigemptyset(&sa.sa_mask);
    sigaction(SIGHUP, &sa, NULL);

    printf("Daemon running with PID %d. Send SIGHUP to reload.\n", getpid());

    while (1) {
        if (g_reload_config) {
            g_reload_config = 0;
            printf("Caught SIGHUP: Reloading configuration and re-opening log files...\n");
            // load_new_config();
            // reopen_log_files();
        }

        sleep(1);
    }

    return 0;
}

Production Examples in Industry

  • Nginx (nginx -s reload): Master process catches SIGHUP, validates configuration syntax, spawns new worker processes running the new configuration, and sends SIGQUIT to old workers to gracefully finish existing HTTP requests.
  • Systemd (systemctl reload <service>): Issues kill -HUP to services configured with ExecReload=/bin/kill -HUP $MAINPID.
  • Logrotate: After renaming log files (e.g. access.log $\rightarrow$ access.log.1), logrotate sends SIGHUP to the daemon so it re-opens file descriptors pointing to the new log path.

Frequently Asked Questions (FAQ)

Why does closing an SSH session kill background processes started with &?

Because when the SSH connection disconnects, the kernel delivers SIGHUP to the login shell, which broadcasts SIGHUP to every process in its session job table. An ampersand (&) only places a process in the background within the current session; it does not detach the process from the shell's session or protect it from SIGHUP.

Why do server daemons use SIGHUP for configuration reloads?

Because daemons have no controlling terminal, SIGHUP is permanently unused for its original purpose, making it the universally recognized convention for non-disruptive configuration reloads and log rotation. Using SIGHUP allows administrators to update certificates, routing rules, or logging paths without restarting the process or terminating existing connections.

Why must a SIGHUP handler avoid reloading configuration files directly?

Because parsing configuration files requires heap allocation (malloc), standard I/O, or string operations that are strictly not async-signal-safe. If SIGHUP interrupts a thread during a memory allocation, running another allocation inside the handler will deadlock the process. The handler must only set an atomic flag for the main event loop to inspect.

Related Topics