November 30, 2025

Linux Daemon

Linux Daemon

Understanding Linux Daemons (with a simple Practical C Example)

Linux systems rely heavily on background processes that run without direct user interaction. These long‑running background processes are called daemons. They typically detach from the controlling terminal and continue running independently, providing core services such as logging, scheduling, networking, and more.

In this post, we will:

  • Implement a minimal daemon in C.
  • Walk through each step required to correctly daemonize a process.
  • Briefly discuss how this relates to modern systemd based service management.

Prerequisites

To follow along with the code examples, you should have:

  • A basic understanding of C programming.
  • Familiarity with Linux command line and system calls. (e.g., fork(), setsid(), chdir(), umask(), signal()).
  • Understanding of process management in Linux.

Why Daemons Matter

Daemons power many of the services users rely on every day. Examples include:

  • Web servers (e.g., Apache, Nginx)
  • Job schedulers (e.g., cron)
  • Logging services (e.g., rsyslog, journald)
  • Network and system services (e.g., sshd, NetworkManager)
systemctl list-units -t service # list the active services (daemons) on a systemd-based Linux system

These processes are designed to be robust, long-lived, and independent of any interactive user session. A properly written daemon continues running even after the user who started it logs out or the terminal closes.

A Comprehensive Daemon in C

This example demonstrates every major step of the daemonization process, including the famous double fork, detaching from terminals, writing a PID file, signal handling, and logging to a file.

1#include <stdio.h>
2#include <stdlib.h>
3#include <unistd.h>
4#include <signal.h>
5#include <sys/types.h>
6#include <sys/stat.h>
7#include <time.h>
8#include <fcntl.h>
9#include <string.h>
10
11// Global flag to signal the daemon to exit
12volatile sig_atomic_t terminate_daemon = 0;
13
14// Signal handler for clean shutdown
15void sig_handler(int signo) {
16 if (signo == SIGTERM || signo == SIGINT) {
17 terminate_daemon = 1;
18 }
19}
20
21int main(int argc, char *argv[]) {
22 pid_t pid;
23
24 // STEP 1: First Fork - Detach from the parent process
25 // We want to detach from the controlling terminal. The parent will exit,
26 // leaving the child to run independently. This is crucial.
27 // Think of it as the child saying, "Bye, mom and dad, I'm off to conquer
28 // the world (or at least log some messages)!"
29 pid = fork();
30 if (pid < 0) {
31 perror("First fork failed");
32 exit(EXIT_FAILURE);
33 }
34 // If we got a positive PID, it means we are the parent.
35 // The parent should exit immediately.
36 if (pid > 0) {
37 exit(EXIT_SUCCESS); // Parent exits, child continues
38 }
39
40 // STEP 2: Create a new session and become a session leader
41 // `setsid()` makes the calling process a session leader and detaches it
42 // from the controlling terminal. It also creates a new process group.
43 // This prevents the daemon from getting any signals from the original
44 // terminal.
45 //
46 // UNDERSTANDING SESSIONS AND PROCESS GROUPS:
47 // - A session is a collection of one or more process groups.
48 // - By calling setsid(), the calling process becomes the session leader.
49 // - The session ID (SID) is set to the calling process's PID.
50 // - The calling process also becomes the leader of a new process group,
51 // and the process group ID (PGID) equals the calling process's PID.
52 // - Most importantly: the process is disassociated from its controlling terminal.
53 // This is crucial for creating daemons that should continue running even
54 // after the user logs out or the terminal is closed.
55 if (setsid() < 0) {
56 perror("setsid failed");
57 exit(EXIT_FAILURE);
58 }
59
60 // STEP 3: Second Fork - Prevent re-acquiring a controlling terminal
61 // WHY A SECOND FORK?
62 // Even after calling setsid(), the process is still a session leader.
63 // On some Unix systems, a session leader can automatically re-acquire
64 // a controlling terminal by opening a terminal device.
65 // By forking again, the new child is no longer a session leader
66 // (its parent is the session leader), which prevents it from ever
67 // re-acquiring a controlling terminal accidentally.
68 // This is an extra safety measure to ensure true daemon behavior.
69 pid = fork();
70 if (pid < 0) {
71 perror("Second fork failed");
72 exit(EXIT_FAILURE);
73 }
74 if (pid > 0) {
75 exit(EXIT_SUCCESS); // First child exits, second child continues
76 }
77
78 // STEP 4: Reset file mode mask
79 // `umask(0)` sets the file mode creation mask to 0 (000 in octal).
80 // This means that new files created by the daemon will have their
81 // permissions determined solely by the permissions specified in the
82 // `open()` or `creat()` call, not modified by the umask.
83 // This ensures predictable file permissions.
84 umask(0);
85
86 // STEP 5: Change the current working directory to the root
87 // This is good practice to prevent the daemon from holding open a
88 // directory on a mounted filesystem that could otherwise be unmounted.
89 // Imagine your daemon preventing you from unmounting a USB drive!
90 if (chdir("/") < 0) {
91 perror("chdir failed");
92 exit(EXIT_FAILURE);
93 }
94
95 // STEP 6: Write the daemon PID to a file
96 // PID files are used to:
97 // 1. Identify the running daemon process
98 // 2. Prevent multiple instances from running simultaneously
99 // 3. Allow management scripts to send signals to the daemon
100 //
101 // Standard locations: /var/run/ or /run/ (requires root/sudo)
102 // For testing without sudo, use /tmp/ instead
103 FILE *pidfile = fopen("/var/run/mydaemon.pid", "w");
104 if (pidfile) {
105 fprintf(pidfile, "%d\n", getpid());
106 fclose(pidfile);
107 } else {
108 // Fallback: if we can't write to /var/run (no sudo), inform via printf
109 // Note: This won't be visible after we close STDOUT, but it's here for
110 // early debugging before the daemon fully detaches
111 printf("Daemon PID: %d\n", getpid());
112 perror("Failed to write PID file to /var/run/mydaemon.pid");
113 // Not exiting - daemon can continue without PID file
114 }
115
116 // STEP 7: Register signal handlers for graceful shutdown
117 // We want our daemon to respond politely to signals like SIGTERM (terminate)
118 // and SIGINT (interrupt) so it can clean up before exiting.
119 signal(SIGTERM, sig_handler);
120 signal(SIGINT, sig_handler);
121
122 // STEP 8: Close standard file descriptors
123 // Daemons should not inherit open file descriptors from the parent.
124 // We want to ensure it has no ties to the original terminal.
125 // STDIN (0), STDOUT (1), and STDERR (2) are closed to fully detach
126 // from the terminal. This prevents accidental reads/writes to a
127 // nonexistent terminal and makes the daemon truly independent.
128 close(STDIN_FILENO); // 0
129 close(STDOUT_FILENO); // 1
130 close(STDERR_FILENO); // 2
131
132 // STEP 9: Main daemon loop - Write timestamps to log file
133 // The daemon now enters its infinite loop where it performs its actual work.
134 // In this example, it writes a timestamp to a log file every 2 seconds.
135 while (!terminate_daemon) {
136 // Open log file in append mode with specific permissions
137 // O_WRONLY: write-only
138 // O_CREAT: create if doesn't exist
139 // O_APPEND: write at the end of the file
140 // 0644: rw-r--r-- (owner can read/write, others can read)
141 int fd = open("/tmp/mydaemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
142
143 if (fd != -1) {
144 // Get current time and convert to string
145 time_t now = time(NULL);
146 char *timestamp = ctime(&now);
147
148 // Write timestamp to log file
149 write(fd, timestamp, strlen(timestamp));
150 close(fd);
151 }
152
153 sleep(2); // Sleep for 2 seconds before next log entry
154 }
155
156 // Clean exit when terminate signal is received
157 // Optionally, you could log a shutdown message here
158 // and remove the PID file
159 unlink("/var/run/mydaemon.pid"); // Clean up PID file
160 exit(EXIT_SUCCESS);
161}

Breaking Down the Daemonization Steps

Each step in the code above corresponds to a well‑established pattern for turning a normal process into a daemon.

1. First fork() – Detach from the parent

  • Concept: fork() creates a child process. The parent exits, and the child continues execution.
  • System call: pid_t fork(void);
  • Purpose: Ensures the new process is not a process group leader, which is required before calling setsid(). It also detaches from the original shell/terminal.
  • Effect: The controlling terminal no longer manages the daemon's lifecycle.
  • Return values:
    • < 0: Fork failed
    • 0: You are in the child process
    • > 0: You are in the parent process, and the value is the child's PID

2. setsid() – Create a new session

  • Concept: setsid() creates a new session and makes the calling process the session leader of that session.
  • System call: pid_t setsid(void);
  • Purpose: Detaches the process from any controlling terminal and process group.
  • What happens internally:
    • The calling process becomes a session leader
    • The session ID (SID) is set to the calling process's PID
    • A new process group is created with PGID = calling process PID
    • The process is disassociated from its controlling terminal
  • Effect: The daemon is isolated from terminal signals such as Ctrl+C in the original shell.
  • Why it matters: This is crucial for creating daemons that continue running even after the user logs out or the terminal is closed.

3. Second fork() – Prevent re-acquiring a controlling terminal

  • Concept: Fork again after setsid() to ensure the process can never acquire a controlling terminal.
  • System call: pid_t fork(void); (called a second time)
  • Purpose: Even after setsid(), a session leader can still open a terminal device and automatically make it the controlling terminal. By forking again, the new child is no longer a session leader, preventing this behavior.
  • Effect: Guarantees true daemon behavior with no possibility of terminal reattachment.
  • This is the "double fork" technique: First fork detaches from parent, second fork prevents terminal acquisition.

4. umask(0) – Control file permissions

  • Concept: Sets the process's file mode creation mask to 0.
  • System call: mode_t umask(mode_t mask);
  • Purpose: Ensures file permissions for newly created files are determined explicitly by the program, rather than being further restricted by an inherited umask.
  • Effect: More predictable file and directory permissions.
  • Example: If you create a file with open(path, O_CREAT, 0644):
    • Without umask(0): Final permissions depend on inherited umask (might be 0022, resulting in 0644 & ~0022 = 0644)
    • With umask(0): Final permissions are exactly 0644 (rw-r--r--)

5. chdir("/") – Change working directory

  • Concept: Changes the current working directory to root.
  • System call: int chdir(const char *path);
  • Purpose: Prevents the daemon from keeping any filesystem (e.g., a mounted volume) unnecessarily busy.
  • Effect: Ensures that unmount operations are not blocked by a daemon holding open a directory.
  • Real-world scenario: Imagine your daemon is started from /mnt/usb/. If it doesn't change directory, you can't unmount /mnt/usb/ while the daemon runs.

6. Writing a PID File

  • Concept: Write the daemon's process ID to a file for management and monitoring.
  • System calls: FILE *fopen(const char *pathname, const char *mode);, int fprintf(FILE *stream, const char *format, ...);
  • Purpose:
    • Allows system administrators to identify the running daemon
    • Prevents multiple instances from running simultaneously (by checking if PID file exists)
    • Management scripts can read the PID to send signals (e.g., kill -SIGTERM $(cat /var/run/mydaemon.pid))
  • Standard locations:
    • /var/run/daemon-name.pid (traditional, requires root)
    • /run/daemon-name.pid (modern, symlink to /var/run/)
    • /tmp/daemon-name.pid (for testing without root privileges)
  • Effect: Provides a reliable way to manage the daemon process.

7. Signal handling

  • Concept: Signals notify a process of asynchronous events, like termination requests.
  • System call: void (*signal(int signum, void (*handler)(int)))(int);
  • Purpose: Handle SIGTERM/SIGINT gracefully, allowing the daemon to perform cleanup before exiting.
  • Common signals:
    • SIGTERM (15): Polite termination request (default for kill)
    • SIGINT (2): Interrupt from keyboard (Ctrl+C)
    • SIGHUP (1): Hangup detected, often used to reload configuration
  • volatile sig_atomic_t: This type ensures the variable can be safely accessed in both signal handlers and main code without data races.
  • Effect: More predictable shutdown behavior and resource cleanup.

8. Closing file descriptors

  • Concept: File descriptors represent open files, sockets, and other I/O resources.
  • System calls: int close(int fd);
  • Purpose: Daemons should not inherit open descriptors from their parent, especially the terminal descriptors (stdin, stdout, stderr).
  • The three standard file descriptors:
    • STDIN_FILENO (0): Standard input
    • STDOUT_FILENO (1): Standard output
    • STDERR_FILENO (2): Standard error
  • Effect: Avoids unintended I/O and resource leaks tied to the original environment. Makes the daemon truly independent.

9. Daemon Main Loop – File Logging

  • Concept: The daemon performs its actual work in an infinite loop.
  • System calls:
    • int open(const char *pathname, int flags, mode_t mode);
    • ssize_t write(int fd, const void *buf, size_t count);
    • time_t time(time_t *tloc);
    • char *ctime(const time_t *timep);
  • Purpose: Demonstrates continuous daemon operation with file-based logging.
  • File open flags:
    • O_WRONLY: Open for writing only
    • O_CREAT: Create file if it doesn't exist
    • O_APPEND: Write data at the end of the file
  • File permissions: 0644 means rw-r--r-- (owner read/write, group read, others read)
  • Effect: Creates a persistent log file that can be monitored to verify daemon operation.

Building and Running the Daemon

  1. Save the code as my_daemon.c.

  2. Compile:

    gcc my_daemon.c -o my_daemon
  3. Run:

    ./my_daemon

    The process will daemonize immediately and the terminal will return control.

  4. Verify that it is running:

    ps aux | grep my_daemon

    You should see an entry showing the daemon process with its PID.

  5. Monitor the log file in real-time:

    tail -f /tmp/mydaemon.log

    This will show timestamps being written every 2 seconds. The -f flag makes tail continuously display new lines as they are added to the file.

  6. Watch the log file with automatic refresh:

    watch -n 1 'tail -10 /tmp/mydaemon.log'

    This uses the watch command to execute tail -10 every 1 second, giving you a continuously updating view of the last 10 log entries.

  7. Check the PID file (if running with sudo):

    cat /var/run/mydaemon.pid

    Or without sudo:

    # The PID was printed to stdout before the daemon detached
  8. Stop the daemon gracefully:

    Using the PID file:

    kill -SIGTERM $(cat /var/run/mydaemon.pid)
    # Or simply:
    kill $(cat /var/run/mydaemon.pid)

    Or using process name:

    pkill my_daemon
    # Or more specific:
    killall my_daemon

    The daemon will handle the signal, clean up (remove PID file), and exit.

  9. Force stop (if graceful stop fails):

    kill -SIGKILL $(cat /var/run/mydaemon.pid)
    # Or:
    pkill -9 my_daemon

Useful Commands for Daemon Management

# View all running daemons (background processes)
ps aux | grep -E 'daemon|[d]$'

# Monitor log file with color highlighting for timestamps
watch -n 1 --color 'tail -10 /tmp/mydaemon.log'

# Count log entries
wc -l /tmp/mydaemon.log

# View logs with timestamps in a more readable format
tail -f /tmp/mydaemon.log | while read line; do echo "[$(date +%H:%M:%S)] $line"; done

# Check if daemon is running and get its PID
pgrep -l my_daemon

# Get detailed process information
ps -fp $(pgrep my_daemon)

# View process tree to see daemon's isolation
pstree -p $(pgrep my_daemon)

Additional Topics for Further Exploration

If you want to extend this example toward real‑world use cases, useful directions include:

  • More robust error handling and retries.
  • Configuration via files under etc.
  • Secure privilege handling (dropping privileges, running under a dedicated user).
  • Inter‑process communication (sockets, message queues, shared memory).
  • Understanding the daemon() library helper function where available.

Appendix: Managing the Daemon with systemd

The previous sections focused on the traditional, manual approach to daemonization. On modern Linux distributions, systemd is usually responsible for starting, supervising, and stopping services. In many production scenarios, you will let systemd handle daemonization and focus on your application logic.

This section shows:

  • How to create a systemd unit for the example daemon.
  • What a “systemd‑native” daemon might look like.
  • How this changes your service configuration.

Running my_daemon Under systemd

Assume my_daemon is compiled and copied to /usr/local/bin/my_daemon.

  1. Create a systemd service unit:

    sudo nano /etc/systemd/system/my_daemon.service

    Example unit file:

    1[Unit]
    2Description=My Simple Daemon
    3After=network.target
    4
    5[Service]
    6Type=forking
    7ExecStart=/usr/local/bin/my_daemon
    8ExecStop=/usr/bin/pkill my_daemon
    9Restart=on-failure
    10User=root
    11Group=root
    12
    13[Install]
    14WantedBy=multi-user.target

    Key points:

    • Type=forking is appropriate because the example program performs its own fork() and becomes a background process.
    • ExecStart points to the compiled binary.
    • ExecStop uses pkill for a simple, signal‑based stop. In more advanced cases, you may implement a dedicated shutdown mechanism.
    • For production, replace root with a dedicated, unprivileged user and group.
  2. Reload systemd configuration:

    sudo systemctl daemon-reload
  3. Enable the service at boot:

    sudo systemctl enable my_daemon.service
  4. Start the service:

    sudo systemctl start my_daemon.service
  5. Check status:

    sudo systemctl status my_daemon.service
  6. Inspect logs:

    sudo journalctl -u my_daemon.service -f
  7. Stop and disable if needed:

    sudo systemctl stop my_daemon.service
    sudo systemctl disable my_daemon.service

A Systemd‑Native Daemon

When you design a daemon specifically for systemd, you typically avoid manual daemonization steps. systemd takes care of session management, working directory, file descriptors, and logging.

Below is a simplified example that relies on systemd for process management:

1#include <stdio.h>
2#include <stdlib.h>
3#include <unistd.h>
4#include <signal.h>
5
6volatile sig_atomic_t terminate_daemon = 0;
7
8void sig_handler(int signo) {
9 if (signo == SIGTERM || signo == SIGINT) {
10 terminate_daemon = 1;
11 }
12}
13
14int main(void) {
15 signal(SIGTERM, sig_handler);
16 signal(SIGINT, sig_handler);
17
18 fprintf(stdout, "Systemd-native daemon started. PID: %d\n", getpid());
19 fflush(stdout);
20
21 while (!terminate_daemon) {
22 fprintf(stdout, "Systemd-native daemon is running. PID: %d\n", getpid());
23 fflush(stdout);
24 sleep(5);
25 }
26
27 fprintf(stdout, "Systemd-native daemon exiting.\n");
28 fflush(stdout);
29 return EXIT_SUCCESS;
30}

A corresponding systemd unit file for this style might look like:

1[Unit]
2Description=My Systemd-Native Daemon
3After=network.target
4
5[Service]
6Type=simple
7ExecStart=/usr/local/bin/my_systemd_daemon
8Restart=on-failure
9User=daemonuser
10Group=daemonuser
11
12[Install]
13WantedBy=multi-user.target

Here:

  • Type=simple is appropriate because the process does not fork() itself into the background.
  • Logging is done via stdout/stderr and captured by systemd’s journal.

Summary

  • The main body of this post focused on the classic daemonization steps implemented directly in C.
  • In modern systems, systemd is typically responsible for supervising daemons, providing process lifecycle management, restart policies, and centralized logging.
  • Understanding both approaches helps when debugging existing services or designing new ones that integrate cleanly with the host system.