100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Bash

Writing Daemons in Bash

Learn the practical patterns for turning a bash script into a well-behaved background daemon: detaching from the terminal, PID files, logging, signal handling, and systemd integration.

Process & Job ControlAdvanced11 min readJul 10, 2026
Analogies

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.

bash
#!/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.
  • setsid starts a process in a new session, detaching it from the terminal's process group entirely.
  • PID files must be verified with kill -0 before trusting them, since stale files from crashed processes are common.
  • flock on 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

Was this page helpful?

Topics covered

#Bash#AdvancedBashScriptingStudyNotes#DevOps#WritingDaemonsInBash#Writing#Daemons#Makes#Script#StudyNotes#SkillVeris