Writing a Proper Linux Daemon in Swift with Signal Handling and Cleanup

Creating a production-grade Linux daemon requires more than simply running a program in the background.
A correct daemon must detach from the controlling terminal, manage lifecycle files, respond predictably to operating system signals, and clean up all resources before termination.
This article explains the architecture and implementation of signal_15.swift, a Swift-based Linux daemon that correctly implements POSIX daemonization and signal-based cleanup.
It follows the classic UNIX daemon model and demonstrates that Swift can be used for serious Linux systems programming—not just application-level code.
Key Concepts
A correct Linux daemon must address the following responsibilities:
- Process detachment and session management
- Lifecycle visibility using a PID file
- Liveness indication using a heartbeat file
- POSIX-compliant signal handling
- Predictable and safe shutdown semantics
This implementation focuses on all of the above.
Daemonization
A proper Linux daemon follows the classic double-fork with session detachment pattern.
This ensures the process:
- Has no controlling terminal
- Cannot accidentally reacquire a terminal
- Is fully detached from the user session
Why Double-Fork?
- Prevents terminal reattachment
- Matches traditional UNIX daemon behavior
- Required for long-running background services
Required Steps
- Fork once and let the parent exit
- Call
setsid()to create a new session - Fork again so the daemon is not a session leader
- Redirect standard file descriptors to
/dev/null
Swift Implementation
if fork() > 0 { exit(0) }
setsid()
if fork() > 0 { exit(0) }
let fd = open("/dev/null", O_RDWR)
dup2(fd, STDIN_FILENO)
dup2(fd, STDOUT_FILENO)
dup2(fd, STDERR_FILENO)
PID Files and Heartbeat Files
Lifecycle files are critical for daemon management and observability.
They allow external tools, administrators, and supervisors to reason about the daemon’s state without attaching debuggers or parsing logs.
PID File
A PID file records the process ID of the running daemon.
Purpose
- Allows
systemd, scripts, or administrators to locate the daemon process - Enables clean termination using
kill - Prevents multiple instances of the daemon from running simultaneously
Behavioral Rules
- Write the PID file only after successful daemonization
- Remove the PID file during graceful shutdown
- Never leave stale PID files behind
Failure to manage the PID file correctly can lead to:
- Orphaned processes
- Broken service restarts
- Incorrect health reporting by supervisors
Heartbeat File
A heartbeat file proves that the daemon is alive without parsing logs.
Purpose
- Lightweight health check mechanism
- Human-readable liveness signal
- Works even when logs are rotated, unavailable, or redirected
Heartbeat files are especially useful for:
- Simple monitoring scripts
- Debugging hung or stalled daemons
- Environments without centralized logging
Logging Strategy
Daemons must never write to a terminal.
Once daemonized, stdin, stdout, and stderr are redirected and should not be relied upon.
Recommended Logging Options
- Dedicated log file (manual rotation or
logrotate) - syslog integration
- journalctl (when managed via
systemd)
A correct logging strategy ensures:
- Logs survive restarts
- Diagnostics are available post-crash
- No accidental dependency on terminal I/O
Signal Handler Design in Swift
A production-grade daemon must handle OS termination signals explicitly.
Failing to do so results in stale PID files, leaked file descriptors, and undefined shutdown behavior.
In UNIX systems, signal handlers have strict constraints:
- They cannot accept custom arguments
- They must execute quickly
- They must operate on globally accessible state
Because of this, any resources that require cleanup—such as PID files or open file handles—must be stored in global variables so that both main() and the signal handler can access them.
Global State for Cleanup
let pidFilePath = "/var/run/my-daemon.pid"
var aliveFileHandle: FileHandle? = nil
signal_15.swift demonstrates that Swift is not just for iOS or server frameworks; it is a capable systems programming language for long-running background processes on Linux.



