SIGHUP
SIGHUP is a POSIX-defined signal in Unix-like operating systems, assigned the number 1, that notifies a process of a hangup condition, such as the closure of its controlling terminal or the death of the controlling process.[1] The default action upon receipt of SIGHUP is to terminate the process abnormally, typically with an exit status indicating termination by the signal.[2]
Historically rooted in early Unix systems, SIGHUP was designed to inform processes of a serial line disconnection, known as a "hangup," which could occur when a modem connection was lost.[2] In modern contexts, the kernel sends this signal to all processes in the foreground process group when the terminal driver detects such an event, for instance, during user logout or network session termination.[2] Processes can handle SIGHUP via signal handlers, allowing them to perform cleanup or other actions before termination.[1]
Beyond its original purpose, SIGHUP has become a conventional mechanism for daemon processes to reload configuration files without interrupting service.[3] For example, the NGINX web server responds to SIGHUP by re-reading its configuration and starting new worker processes while gracefully shutting down old ones, enabling zero-downtime updates.[4] Similarly, tools like the kill utility allow administrators to send SIGHUP manually to trigger these behaviors in running services.[5] This usage aligns with best practices for system administration, promoting reliability in long-running applications.
Overview
Definition and Purpose
SIGHUP, short for "signal hangup," is a POSIX-defined signal sent to a process to indicate that its controlling terminal has closed or that the controlling process has terminated.[2] Historically rooted in notifying processes of a serial line disconnection—referred to as a "hangup"—it has evolved to serve a broader role in signaling loss of process control, such as in daemon management where it often triggers configuration reloads without termination.[6] In POSIX.1-1990, SIGHUP is standardized as signal number 1 across compliant systems, with a default action of terminating the receiving process to facilitate cleanup.[7]
The primary purpose of SIGHUP is to ensure orderly termination of processes tied to a user session when the controlling terminal becomes unavailable, preventing orphaned or lingering computations that could consume resources.[2] This mechanism promotes system hygiene by prompting processes to release resources and exit gracefully upon detecting session loss. For instance, in an interactive shell environment, when a user logs out, the shell sends SIGHUP to all foreground and certain background jobs (unless explicitly disowned), enabling them to terminate and clean up associated files or connections.[8]
Unlike SIGTERM, which requests general process termination, SIGHUP specifically addresses terminal-related disruptions, though both default to process termination.[2]
Signal Mechanics
SIGHUP is an asynchronous signal delivered by the operating system's kernel through its standard signal mechanism, which interrupts the normal execution of a process or thread upon receipt.[2] This delivery occurs when the kernel detects specific events, such as a hangup on the controlling terminal, and the signal is transmitted as a process-directed notification to an unblocked thread within the target process.[2]
In the delivery process, the kernel sends SIGHUP to all members of the foreground process group associated with the terminal, ensuring that related processes are notified collectively.[2] For standard signals like SIGHUP, the kernel maintains a pending signal set using bitmasks; multiple instances generated while blocked result in only one pending signal. Real-time signals, however, support queued delivery of multiple instances in FIFO order.[2] Upon kernel-to-user-space transitions, such as system call returns, the kernel checks for pending signals and delivers them if unblocked, invoking a handler or default action as appropriate.[2]
Processes can block SIGHUP to prevent its delivery using system calls like sigprocmask(2) in single-threaded programs or pthread_sigmask(3) in multithreaded environments, which modify the signal mask to add SIGHUP to the blocked set until explicitly unblocked.[9][10] Blocking defers delivery without discarding the signal, which remains pending in the process's signal set.[2]
If no custom handler is installed for SIGHUP, the default action upon receipt is to terminate the process without generating a core dump, distinguishing it from signals like SIGSEGV that produce a core file for debugging.[2] This behavior aligns with POSIX.1-1990 standards for reliable signal handling.[7]
History
Origins in Early Unix
SIGHUP was introduced in Version 4 of Unix in 1973 by developers at Bell Labs as part of the evolving signal system designed to handle asynchronous events, including terminal-related interruptions. The signal system provided mechanisms for processes to respond to hardware faults and user-initiated events, with early signals like interrupt (SIGINT) and quit (SIGQUIT) enabling programs to catch or ignore them via dedicated system calls. SIGHUP, designated as signal number 1, was specifically modeled after teletype "hangup" events, where a physical disconnection of the serial line—common in the era's terminal setups—would signal the end of a user session, emphasizing its critical role in session management and process termination.[11][12]
In Version 6 Unix, released in 1975, the implementation of SIGHUP became more explicitly tied to the terminal (tty) driver, which monitored the carrier detect line for loss of connection. Upon detecting such a hangup, the driver would send SIGHUP to all processes in the foreground process group, ensuring that interactive programs could terminate gracefully or perform cleanup before exiting. This mechanism was defined in the system's parameter header, where SIGHUP is enumerated as signal 1 with the comment "hangup," reflecting its origin in notifying processes of terminal disconnections. The tty driver's handling integrated with broader signal delivery routines, allowing processes to register handlers for SIGHUP alongside other terminal signals like SIGINT for rubout (interrupt).[13]
The design of SIGHUP and the early Unix signal system drew influences from the Multics operating system, where more complex interprocess communication and resource management features inspired Unix's developers, but was significantly simplified to accommodate the hardware constraints of the PDP-11 minicomputer, such as limited memory and processing power. This simplification prioritized essential terminal event handling without the overhead of Multics' segmented addressing or comprehensive protection rings, making SIGHUP a lightweight yet effective tool for managing session lifecycle in a multi-user environment.[14]
Standardization and Evolution
SIGHUP was first formally standardized in POSIX.1-1988 (IEEE Std 1003.1-1988), where it was designated as signal number 1 with a default action of abnormal termination (denoted as "T" in the standard's signal table).[7] This specification ensured portability across conforming Unix-like systems by mandating SIGHUP's behavior as a notification for hangup events on the controlling terminal, requiring implementations to deliver the signal reliably to processes.[7]
During the 1980s, Berkeley Software Distribution (BSD) Unix extended SIGHUP's role to support job control facilities, introduced in releases like 4.1BSD (1981), where interactive shells would propagate the signal to background or stopped jobs upon logout or terminal closure to facilitate process cleanup.[2] The nohup utility, introduced in early Unix versions such as Version 5 (1974), allows long-running processes to ignore SIGHUP and persist beyond session termination without modification; this utility sets the signal disposition to ignored before invoking the target command and was later standardized in POSIX.[15]
The Linux kernel, initiated in 1991, inherited SIGHUP's core semantics from both AT&T System V and BSD traditions, maintaining its default termination action while adhering to POSIX.1-1990 requirements.[2] To support real-time extensions outlined in POSIX.1-2001, Linux introduced enhancements like the SA_SIGINFO flag in the sigaction() system call, enabling signal handlers to receive additional context (e.g., sender process ID and signal value) for more precise handling of SIGHUP and other signals in concurrent environments.[2]
Modern platforms such as macOS and FreeBSD uphold POSIX compliance for SIGHUP, with signal number 1 and default termination, though macOS integrates it into the XNU kernel's hybrid architecture via a BSD compatibility layer for user-space POSIX interfaces, introducing minor adaptations for Mach task management without altering core signal delivery.[7] FreeBSD similarly prioritizes POSIX.1 conformance in its kernel signal handling, ensuring SIGHUP's propagation aligns with standard expectations while supporting extensions for contemporary job control.[16]
Generation
The primary trigger for generating a SIGHUP signal occurs when a process's controlling terminal is closed, such as during user logout from a shell using the exit command or by pressing Ctrl+D in Bash.[2] This event is detected by the kernel, which sends the signal to the entire foreground process group associated with the session via the tty (teletype) layer.[2] Specifically, the signal is generated when the session leader process exits or when the carrier signal on the terminal line is lost, ensuring that processes tied to the terminal are notified of the disconnection.[2]
Additional triggers include the termination of remote sessions, such as in SSH or Telnet connections, where the network disconnection effectively closes the pseudo-terminal, prompting the kernel to issue SIGHUP to the relevant process group.[2]
A key example arises in multi-user systems, where a user logging out from their shell sends SIGHUP to all processes in the foreground process group, including any subshells and their child processes, unless the signal is explicitly ignored by those processes.[2] By default, receipt of SIGHUP terminates these processes, cleaning up session resources upon terminal closure.[2]
Manual and Programmatic Sending
SIGHUP can be sent manually from the command line using the kill utility, a POSIX-standard tool that transmits signals to specified processes or process groups. To target a specific process, the command kill -HUP <pid> or equivalently kill -1 <pid> is employed, where <pid> is the process identifier obtained via tools like ps. For sending to an entire process group, kill -HUP -<pgid> is used, with <pgid> denoting the process group ID.[5][17]
Programmatically, applications can dispatch SIGHUP using system calls defined in POSIX. In C, the kill() function from <signal.h> sends the signal to a single process: kill(pid, SIGHUP);, where pid is the target process ID and SIGHUP (or 1) specifies the signal. To broadcast to a process group, killpg(pgid, SIGHUP); is invoked, requiring <sys/types.h> and appropriate privileges for the sender. These calls enable inter-process communication for tasks like reconfiguration without relying on terminal events.[18][19]
In shell environments like Bash, SIGHUP transmission leverages job control features. The built-in kill command supports kill -HUP %<job> to signal a background job by its specifier (e.g., %1 for the first job), facilitating management of suspended or running tasks. This integrates with Bash's job control, where jobs lists active jobs for identification.
Handling and Behavior
Default Process Response
Upon receiving the SIGHUP signal without a custom handler, a process undergoes immediate abnormal termination, equivalent to invoking the _exit() function with an implementation-defined status indicating termination due to the signal. This action does not generate a core dump file, distinguishing SIGHUP from signals like SIGSEGV or SIGABRT that include core dumping by default.[2]
The POSIX standard mandates this termination behavior for SIGHUP, ensuring no automatic invocation of cleanup routines such as those registered via atexit() or flushing of open streams, as the process ends abruptly without executing normal exit processing.[20] Receipt of SIGHUP also interrupts any ongoing system calls in the process, typically returning an EINTR error to allow the calling code to handle the interruption if applicable.[2] Following termination, the process enters a zombie state, retaining a minimal entry in the process table until its parent reaps it via wait() or waitpid(), at which point resources are fully released.
In practice, this default response extends to undetached child processes in scenarios like an interactive shell receiving SIGHUP upon user logout; the shell terminates while first propagating SIGHUP to its jobs (running or stopped processes), causing them to terminate unless they ignore the signal.[21] For example, a simple foreground shell script, such as one echoing output in a loop, will exit immediately upon SIGHUP from terminal closure, freeing associated resources without further execution.[21][2]
Custom Signal Handlers
In Unix-like systems, developers can override the default response to SIGHUP by installing custom signal handlers using the signal() or sigaction() functions in C. The signal() function provides a simpler interface for setting a handler but may not offer full control over signal disposition, while sigaction() allows more precise configuration, including flags like SA_RESTART, which ensures that certain interrupted system calls are automatically restarted rather than returning an error.[22][23]
Common patterns for SIGHUP handlers include reloading configuration files, reopening log files to rotate them without downtime, or performing a graceful shutdown by closing resources and exiting the process. For instance, a basic C handler using sigaction() might look like this, where the handler simply prints a message and exits:
c
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void sighup_handler(int sig) {
write(STDERR_FILENO, "Received SIGHUP, reloading configuration...\n", 41);
// Perform reload logic here
exit(0);
}
int main() {
struct sigaction sa;
sa.sa_handler = sighup_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGHUP, &sa, NULL);
while (1) {
sleep(1); // Main loop
}
return 0;
}
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void sighup_handler(int sig) {
write(STDERR_FILENO, "Received SIGHUP, reloading configuration...\n", 41);
// Perform reload logic here
exit(0);
}
int main() {
struct sigaction sa;
sa.sa_handler = sighup_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGHUP, &sa, NULL);
while (1) {
sleep(1); // Main loop
}
return 0;
}
This example sets the handler with SA_RESTART to resume interrupted calls like sleep().[23]
Under POSIX standards, signal handlers execute in a separate execution context from the interrupted process, which can lead to race conditions or deadlocks if non-reentrant functions are called; therefore, handlers must invoke only async-signal-safe functions, such as write() or signal(), to ensure reliability.[24]
In higher-level languages, similar interception is supported. For Node.js, the process.on('SIGHUP', handler) method registers a callback to handle the signal, often used for reloading modules or configurations without terminating the process.[25] In Python, the signal.signal(signal.SIGHUP, handler) function installs a custom handler, enabling responses like reconfiguration in long-running scripts.[26]
Applications
Usage in Daemons and Servers
In Unix-like systems, daemons—long-running background processes such as network servers—typically detach from any controlling terminal during initialization by invoking the setsid() system call, which establishes a new session and process group leader, thereby immunizing them against the automatic SIGHUP delivered upon terminal hangup or user logout. This detachment ensures uninterrupted operation independent of user sessions, while preserving the ability to receive manually dispatched SIGHUP signals for administrative purposes.
A prevalent convention among daemons is to interpret manually sent SIGHUP as a cue to reload configuration files without terminating the process, enabling seamless updates in production environments. This pattern is integrated into modern init systems like systemd, where SIGHUP is commonly used as the reload signal for services via the ExecReload= directive, allowing administrators to apply changes—such as updated parameters or certificates—without downtime by executing commands like systemctl reload <service>.
Prominent examples illustrate this usage in network services. The Nginx web server, upon receiving SIGHUP at its master process, validates and reloads its configuration files, then gracefully restarts worker processes to incorporate changes while handling existing connections uninterrupted.[4] Likewise, the OpenSSH daemon (sshd) responds to SIGHUP by re-executing itself with the original startup arguments, thereby rereading /etc/ssh/sshd_config to apply modifications, including host key rotations, without disrupting active sessions.
Configuration Management Patterns
One prominent configuration management pattern utilizing SIGHUP is the reload pattern, where a long-running process intercepts the signal to reread its configuration files and apply changes dynamically without interrupting ongoing operations. This approach enables hot-reloading, allowing administrators to update settings—such as port bindings, logging levels, or resource limits—while the process continues serving requests, avoiding the full termination associated with signals like SIGTERM.[27][28]
To ensure atomicity and zero-downtime updates, processes often implement the pattern by forking child instances that load the new configuration; existing children handle legacy requests until they complete, after which they exit gracefully. For instance, the NGINX web server employs this on SIGHUP by reloading its configuration and spawning new worker processes, which inherit the updated settings while old workers phase out.[29]
This pattern is particularly valuable in logging daemons, where SIGHUP prompts reopening of log files after rotation, preventing data loss or writes to archived files without requiring a service restart. Tools like logrotate automate this by executing post-rotation scripts that send SIGHUP to affected daemons, such as syslogd.[30]
Key implementations include the Postfix mail transfer agent, which rereads its main.cf and master.cf files on SIGHUP, terminating only removed services immediately while allowing others to finish naturally, thus applying map and queue changes seamlessly.[27] Additionally, custom scripts scheduled via cron jobs can periodically send SIGHUP to processes, enabling automated checks and reloads for evolving configurations in non-daemon environments.[31]
Best practices for SIGHUP handlers emphasize idempotency, ensuring that repeated signals produce consistent results without side effects, such as duplicate reloads or resource leaks; this safeguards against accidental multiple transmissions during administrative tasks.[32]
Comparison with Other Termination Signals
SIGHUP shares the default action of terminating the receiving process with SIGTERM and SIGINT, both of which are standard POSIX termination signals designed to request process shutdown. However, SIGHUP is specifically triggered by the closure of a controlling terminal or the death of the controlling process, making it inherently session- and terminal-oriented, whereas SIGTERM serves as a general-purpose termination request often initiated manually via tools like the kill command for graceful shutdowns across any context. This distinction positions SIGHUP as a more specialized signal for handling environmental changes in interactive sessions, while SIGTERM provides broader applicability for administrative or programmatic control without ties to terminal state.[33][2]
In contrast to SIGINT, which is generated by user intervention such as pressing Ctrl+C to interrupt execution in the foreground, SIGHUP arises from passive system events like terminal disconnection, emphasizing environmental disruption over active user intent. Both signals target the foreground process group by default when originating from the terminal—SIGINT for immediate interruption and SIGHUP for hangup notification—but their triggers differentiate their use cases: SIGINT for interactive control during runtime, and SIGHUP for post-session cleanup. This makes SIGHUP less about user-driven abortion and more about ensuring processes respond to loss of their interactive environment.[33][2]
Like SIGTERM and SIGINT, SIGHUP is catchable and ignorable by processes, allowing custom handlers to perform cleanup or reconfiguration before termination, in sharp contrast to the non-ignorable SIGKILL, which forces immediate process death without intervention. SIGHUP is frequently ignored via utilities like nohup to enable detached operation beyond terminal sessions, a practice less common for SIGTERM or SIGINT due to their non-environmental triggers. This ignorability supports SIGHUP's role in reload patterns for daemons, where termination is avoided to facilitate configuration updates.[15][2]
A key operational difference lies in targeting: SIGHUP is automatically broadcast to the entire foreground process group upon terminal hangup or session leader exit, ensuring coordinated response across related processes, while SIGTERM defaults to per-process delivery unless explicitly directed to a group via tools like kill -TERM -PGID. This group-oriented nature of SIGHUP aligns with its purpose in managing session-wide events, promoting collective termination or handling in multi-process terminal interactions, unlike the individualistic focus of SIGTERM.[33][2]
Linux provides full POSIX-compliant support for SIGHUP, adhering to the standard semantics where the signal is generated upon terminal closure or process detachment from its controlling terminal. Beyond POSIX, Linux extends signal handling with mechanisms like signalfd(), a Linux-specific system call introduced in kernel 2.6.22 that creates a file descriptor for reading pending signals, including SIGHUP, allowing integration with I/O multiplexing interfaces such as select(2), poll(2), and epoll(7).[34] This enables queued signal delivery without traditional handlers, provided signals are blocked via sigprocmask(2) to avoid default actions. Glibc enhances usability with wrapper functions for signalfd() since version 2.8, automatically employing the signalfd4() variant (available since kernel 2.6.27) for additional flags like non-blocking and close-on-exec behavior starting from glibc 2.9.[34]
BSD-derived systems, including FreeBSD and macOS, inherit core BSD semantics for SIGHUP, where the signal is delivered to foreground processes upon terminal hangup, and interactive shells propagate it to all child jobs—both running and stopped—before exiting upon receiving SIGHUP themselves.[35] This propagation ensures background jobs are notified of session termination, differing slightly from some POSIX interpretations in shell-specific handling, such as csh or tcsh variants that may require explicit disowning to prevent forwarding.[36] In macOS, built on the Darwin kernel combining Mach microkernel with BSD layers, SIGHUP operates within the BSD subsystem for POSIX compatibility, but the platform supplements signal-based notifications with Mach-specific mechanisms like ports and messages for inter-process communication, though these do not directly alter SIGHUP delivery.
Windows lacks a native SIGHUP equivalent in the Win32 API, relying instead on console control events such as CTRL_CLOSE_EVENT to simulate terminal closure, which POSIX layers map to SIGHUP for compatibility. In Cygwin, which emulates a POSIX environment, SIGHUP is supported but exhibits variations in pseudo-terminal (PTY) handling; prior to patches around 2015, extraneous SIGHUP signals were generated during read/write operations on PTY slaves, deviating from POSIX standards that limit delivery to master-side closure when the slave is the controlling terminal.[37] Subsequent updates aligned Cygwin's behavior more closely with systems like FreeBSD by deferring SIGHUP to PTY master close. Similarly, Windows Subsystem for Linux (WSL) emulates Linux signal semantics, including full SIGHUP support via its Linux kernel, but console events from the Windows host—such as window closure—trigger SIGHUP delivery to WSL processes attached to the pseudo-terminal.[38]
In Solaris, SIGHUP follows POSIX standards but incorporates platform-specific constraints related to zones and resource contracts; signals like SIGHUP sent from processes in the global zone to those in non-global zones are ignored unless the sender holds appropriate privileges, enhancing isolation in containerized environments. This inter-zone signal restriction prevents cross-boundary control, differing from unified namespace systems, while within a zone, SIGHUP behaves standardly for terminal-related events.