How to Detach a Daemon Process in C

In Unix-like operating systems, a daemon is a long-running background service that operates independently of any user session or controlling terminal. To prevent a process from receiving terminal-generated signals (such as SIGHUP or SIGINT) and blocking system shutdowns or filesystem unmounts, it must properly disassociate from the terminal that spawned it. In C, this detachment requires a defined sequence of POSIX system calls: forking to drop terminal association, creating an independent session with setsid(), preventing terminal reacquisition, resetting file masks, changing the working directory, and redirecting standard I/O streams.

1. The Initial Fork and Parent Termination

The first step is invoking fork(). The parent process exits immediately upon a successful fork, returning control to the terminal shell. This serves two purposes: the shell perceives the command as completed, and the surviving child process is guaranteed not to be a process group leader, which is an absolute prerequisite for the next step.

pid_t pid = fork();
if (pid < 0) {
    exit(EXIT_FAILURE);
}
if (pid > 0) {
    exit(EXIT_SUCCESS); /* Parent exits */
}

2. Creating a New Session with setsid()

The child process calls setsid() to become the leader of a new session and the leader of a new process group. Because the process is not currently a process group leader, setsid() succeeds.

At this exact point, the process is completely severed from its controlling terminal (controlling TTY). Signals sent to the original terminal will no longer affect this process.

if (setsid() < 0) {
    exit(EXIT_FAILURE);
}

3. The Second Fork (Preventing TTY Reacquisition)

Under System V-derived systems, a session leader without a controlling terminal can automatically acquire one if it opens a terminal device file (such as /dev/tty).

To permanently prevent this, the daemon forks a second time and terminates the first child. The grandchild process belongs to the newly created session, but it is not the session leader. Because only session leaders can acquire controlling terminals, the daemon is permanently protected from acquiring a TTY inadvertently.

pid = fork();
if (pid < 0) {
    exit(EXIT_FAILURE);
}
if (pid > 0) {
    exit(EXIT_SUCCESS); /* First child exits */
}

4. Clearing the File Creation Mask

A daemon inherits its file creation mask (umask) from the parent environment, which may restrict file permissions unexpectedly when the daemon creates logs, configuration files, or sockets. Calling umask(0) ensures the daemon has complete control over the permissions of files it creates.

umask(0);

5. Changing the Working Directory

A running process holds a reference to its current working directory, preventing the underlying filesystem from being unmounted. Daemons typically change their working directory to the root directory (/) or a dedicated service directory (e.g., /var/run/mydaemon).

if (chdir("/") < 0) {
    exit(EXIT_FAILURE);
}

6. Closing and Redirecting File Descriptors

The daemon inherits file descriptors from the parent, specifically standard input (stdin), standard output (stdout), and standard error (stderr). Leaving these open maintains a hidden tie to the terminal and can cause crashes if the daemon attempts output after the terminal closes.

The daemon closes these descriptors and redirects file descriptors 0, 1, and 2 to /dev/null.

close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);

int null_fd = open("/dev/null", O_RDWR);
if (null_fd >= 0) {
    dup2(null_fd, STDIN_FILENO);
    dup2(null_fd, STDOUT_FILENO);
    dup2(null_fd, STDERR_FILENO);
    if (null_fd > STDERR_FILENO) {
        close(null_fd);
    }
}

Complete Implementation Example

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void daemonize(void) {
    pid_t pid;

    /* First fork */
    pid = fork();
    if (pid < 0) exit(EXIT_FAILURE);
    if (pid > 0) exit(EXIT_SUCCESS);

    /* Create new session and detach from controlling TTY */
    if (setsid() < 0) exit(EXIT_FAILURE);

    /* Second fork to prevent acquiring a TTY in the future */
    pid = fork();
    if (pid < 0) exit(EXIT_FAILURE);
    if (pid > 0) exit(EXIT_SUCCESS);

    /* Reset file mask */
    umask(0);

    /* Change directory */
    if (chdir("/") < 0) exit(EXIT_FAILURE);

    /* Redirect standard file descriptors to /dev/null */
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    int fd = open("/dev/null", O_RDWR);
    if (fd != -1) {
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        if (fd > 2) close(fd);
    }
}

int main(void) {
    daemonize();

    /* Daemon background work loop */
    while (1) {
        sleep(60);
    }

    return 0;
}

While GNU/Linux provides a convenience function called daemon(3), executing this manual sequence remains the POSIX-compliant standard for low-level process control, ensuring robust isolation from any terminal interface.