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
// advertisement

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 ` or `strace -e trace=file /usr/sbin/olddaemon` shows exactly which files and sockets the process is touching at startup.

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
// advertisement