What Makes a Process a Daemon
Three conditions define a daemon: it runs in the background, it has no controlling terminal (TTY), and its parent is typically PID 1 (init or systemd) after double-forking. Check the TTY column to confirm:
``` ps -eo pid,ppid,tty,comm | grep '?' ```
A `?` in the TTY column means no controlling terminal - that process is a daemon or at least behaves like one. A regular shell job backgrounded with `&` still holds a TTY from the parent shell session. That distinction matters: if you close the terminal, a simple background job receives SIGHUP and usually dies. A proper daemon does not.
The traditional double-fork technique ensures daemon independence. The process forks once, the parent exits, the child calls `setsid()` to become a session leader, then forks again so it cannot accidentally re-acquire a terminal. systemd handles this automatically for unit-managed services, but if you are writing a C service or a shell-based init script, you still need to understand why the double fork exists.
ps -eo pid,ppid,tty,stat,comm | awk '$3 == "?" {print}' | head -20
How systemd Manages Daemons in 2026
On every major Linux distribution shipping systemd 255 or later, daemons are defined as unit files under `/etc/systemd/system/` or `/usr/lib/systemd/system/`. The unit file replaces the old SysV init script entirely.
A minimal daemon unit looks like this:
```ini [Unit] Description=My background worker After=network.target
[Service] Type=simple User=worker ExecStart=/usr/local/bin/myworker --config /etc/myworker/config.toml Restart=on-failure RestartSec=5s
[Install] WantedBy=multi-user.target ```
The `Type=simple` directive tells systemd that the process does not fork - systemd tracks the main PID directly. Use `Type=forking` only for legacy daemons that still double-fork themselves. Use `Type=notify` if your daemon calls `sd_notify(3)` to signal readiness.
To inspect the current state of any daemon:
```bash systemctl status sshd journalctl -u sshd -n 50 --no-pager ```
The journal is the authoritative log source for systemd-managed daemons. `tail -f /var/log/messages` still works on systems running rsyslogd alongside journald, but for services that write only to the journal, `journalctl -f -u servicename` is the correct tool.
systemctl list-units --type=service --state=running
Common Unix Daemons and What They Actually Do
Knowing the canonical daemons helps when reading process tables on unfamiliar servers. These are the ones you will encounter most often:
- `sshd` - OpenSSH daemon, listens on port 22, forks a child per connection - `crond` - job scheduler, reads `/etc/crontab` and per-user crontabs in `/var/spool/cron/` - `rsyslogd` - syslog implementation, receives log messages via `/dev/log` socket - `nginx` or `httpd` - web server master process, manages worker children - `dockerd` - Docker engine daemon, communicates via `/var/run/docker.sock` - `kubelet` - Kubernetes node agent, manages pod lifecycle on the local node - `ntpd` or `chronyd` - time synchronization - `udevd` (as `systemd-udevd`) - device event management
For any daemon, find its socket or PID file:
```bash ss -tlnp | grep sshd cat /run/sshd.pid ```
Daemons typically write a PID file to `/run/` or `/var/run/` so that init systems and watchdog scripts can check whether the process is alive without scanning the full process table.
ls -la /run/*.pid
Writing a Simple Daemon in Shell
For quick automation tasks - log rotation, health checks, queue draining - a shell daemon is often sufficient. The pattern below properly detaches from the terminal and writes a PID file:
```bash #!/bin/bash DAEMON_PIDFILE=/run/mypoller.pid LOGFILE=/var/log/mypoller.log
if [[ -f $DAEMON_PIDFILE ]] && kill -0 "$(cat $DAEMON_PIDFILE)" 2>/dev/null; then echo "Already running" >&2 exit 1 fi
# Detach setsid bash -c ' echo $$ > '"$DAEMON_PIDFILE"' exec >> '"$LOGFILE"' 2>&1 while true; do date sleep 60 done ' & ```
`setsid` does the session leader work without requiring a double-fork in bash. This is fine for internal tooling. For anything exposed to the network or running as a privileged user, write it in a compiled language or use a mature runtime with proper signal handling.
If you are building custom automation workflows and want to manage multiple such daemons from a central place, tools like taskbotshub.ai handle scheduling, retry logic, and observability for daemon-adjacent background jobs without you maintaining bespoke systemd units for every task.
setsid myworker --daemon &
Debugging a Daemon That Will Not Start
When `systemctl start myservice` fails with a generic error, the first move is always:
```bash journalctl -u myservice -n 100 --no-pager systemctl status myservice ```
Common failure modes:
1. **Permission denied on socket or file** - check `ls -la` on the path and verify the service user matches. 2. **Port already bound** - `ss -tlnp | grep :8080` to find the conflicting process. 3. **Missing dependency** - `systemctl list-dependencies myservice` shows what must be active first. 4. **ExecStart path wrong** - `which myworker` inside the same environment the service runs in, not your interactive shell.
For daemons that start but immediately exit, add `StandardOutput=journal` and `StandardError=journal` to the `[Service]` block if they are not already set, then `systemctl daemon-reload && systemctl restart myservice` before reading the journal again.
For legacy non-systemd daemons, `strace -p
journalctl -u myservice --since '5 min ago' --no-pager
Naming and Registering Your Daemon Project
If you are releasing a daemon as an open source project or registering it as a service, the binary name, the systemd unit name, and any public hostname should be consistent. The convention is lowercase, no spaces, ending in `d` for the binary (e.g., `nginx`, `sshd`, `rsyncd`). When you are also setting up a project site or registering a domain for your daemon project, nicename.me is a quick way to check name availability across domains and namespaces before you commit to a name in your unit files and package metadata.
For the systemd unit file name, match the binary: if the binary is `vaultd`, the unit is `vaultd.service`. This makes `systemctl enable vaultd` and `journalctl -u vaultd` predictable for anyone who installs your software.
systemctl cat sshd.service | head -20