What Makes a Script a Daemon
A daemon is a long-running background process detached from any controlling terminal, typically started at boot or on demand, running with no interactive stdin/stdout and surviving the logout of whoever started it. A bash script becomes daemon-like by combining several techniques: redirecting file descriptors 0/1/2 away from the terminal, calling setsid (or disown plus closing the terminal) to detach from the controlling terminal and process group, and writing a PID file so external tools can find and manage the running instance without relying on ps and pattern matching.
Cricket analogy: A daemon is like a ground curator who works continuously in the background year-round maintaining the pitch, independent of whether any particular match (terminal session) is currently being played, always ready when the next match starts.
#!/usr/bin/env bash
set -euo pipefail
PIDFILE=/var/run/mydaemon.pid
LOGFILE=/var/log/mydaemon.log
if [[ -f "$PIDFILE" ]] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
echo "Already running (PID $(cat "$PIDFILE"))" >&2
exit 1
fi
# Detach: new session, redirect fds, run in background
setsid bash -c '
exec >>"'"$LOGFILE"'" 2>&1
exec </dev/null
echo "$$" > "'"$PIDFILE"'"
cleanup() { rm -f "'"$PIDFILE"'"; echo "Daemon stopping"; exit 0; }
trap cleanup TERM INT
echo "Daemon started at $(date -Iseconds)"
while true; do
echo "heartbeat: $(date -Iseconds)"
sleep 30 &
wait $! # wait interruptibly so trap fires promptly
done
' &
disown
echo "Started daemon, logs at $LOGFILE"PID Files, Locking, and Preventing Duplicate Instances
A PID file records the running instance's process ID so a stop script or health check can find it reliably, but a stale PID file left behind after a crash is a classic bug — always verify the recorded PID is actually alive and belongs to your process (e.g., via kill -0 $pid and checking /proc/$pid/cmdline or ps -p $pid -o comm=) before trusting it, rather than assuming its mere existence means the daemon is running. A more robust alternative to a bare PID file is flock on a lock file, which the kernel automatically releases if the holding process dies for any reason, eliminating the stale-PID-file problem entirely.
Cricket analogy: Trusting a stale PID file without verifying it is like assuming a player is still on the field just because their name is on the team sheet, without checking they haven't already been substituted off — you need to actually check the field (kill -0), not just the paperwork.
flock provides advisory kernel-level locking that's automatically released when the holding file descriptor closes, even on a crash: exec 200>/var/run/mydaemon.lock; flock -n 200 || { echo 'already running'; exit 1; } at the top of a script is a simple, race-free way to prevent duplicate daemon instances without ever worrying about stale PID files.
systemd as the Modern Alternative
On modern Linux systems, hand-rolling detachment logic with setsid and PID files is largely legacy practice — systemd unit files with Type=simple (or Type=notify for scripts using sd_notify) handle process supervision, automatic restart on crash, logging via journald, and clean SIGTERM-based shutdown far more reliably than a bash script managing its own daemonization. In that model, the bash script itself stays simple and foreground (no setsid, no manual fork-and-detach), while systemd supplies the actual daemon infrastructure: ExecStart=/usr/local/bin/myscript.sh, Restart=on-failure, and KillSignal=SIGTERM in the unit file replace dozens of lines of manual daemonization code.
Cricket analogy: Manually hand-rolling daemonization logic is like a team captain personally managing ground maintenance, ticketing, and broadcast logistics themselves, whereas using systemd is like hiring a professional venue management company (the BCCI's ground staff) to handle all that infrastructure so the captain can just focus on playing.
If you still must hand-roll a daemon (e.g., no systemd available, embedded environment), never redirect stdin/stdout/stderr to /dev/null blindly without first redirecting to a real log file — a script that inherits the launching terminal's file descriptors and later has that terminal close can receive SIGHUP or EPIPE errors on write, silently crashing the daemon.
- A daemon detaches from its controlling terminal, redirects standard file descriptors, and survives logout of its launcher.
setsidstarts a process in a new session, detaching it from the terminal's process group entirely.- PID files must be verified with
kill -0before trusting them, since stale files from crashed processes are common. flockon a dedicated lock file descriptor is more robust than PID files since the kernel releases it automatically on crash.- systemd unit files (
Type=simple,Restart=on-failure) replace most hand-rolled daemonization logic on modern Linux. - Trap SIGTERM in any hand-rolled daemon to clean up the PID file and exit gracefully rather than being SIGKILLed.
- Always redirect stdin/stdout/stderr to real files or /dev/null explicitly, or writes can fail once the launching terminal closes.
Practice what you learned
1. What is the primary purpose of calling `setsid` when daemonizing a bash script?
2. Why is checking `kill -0 $(cat pidfile)` important before trusting a PID file?
3. What advantage does `flock` on a lock file have over a plain PID file for preventing duplicate daemon instances?
4. In a modern systemd-managed Linux deployment, what is generally the recommended replacement for hand-rolled bash daemonization?
Was this page helpful?
You May Also Like
trap and Signal Handling
Master bash's `trap` builtin to intercept signals like SIGINT and EXIT, enabling reliable cleanup, graceful shutdown, and robust error handling in scripts.
Background Jobs and Process Management
Learn how to launch, monitor, and control background jobs in bash using &, jobs, fg, bg, wait, and disown for scripts that need concurrency.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics
DevOpsCI/CD Tools & Pipelines Study Notes
YAML · 37 topics