Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Init Systems Compared — systemd, sysvinit, OpenRC, runit, dinit

Overview

The five init systems in this section implement the same job — be PID 1, start and manage services — with five different core bets:

  • systemd bets on a resident engine: a transactional dependency graph, cgroup-scoped supervision, and an ecosystem (journal, timers, resolved, networkd) that absorbs adjacent problems.
  • sysvinit bets on shell scripts and static ordering: maximal transparency, zero magic, no supervision.
  • OpenRC bets on the middle ground: dependency-aware shell services with a real solver, no daemon, supervision optional.
  • runit bets on supervision: every service is a directory with a run script, watched by a tiny supervisor, forever.
  • dinit bets on a modern minimal core: a systemd-style dependency graph with proper failure semantics and supervision, without the ecosystem.

This page lines the five up: feature matrix, architecture, command porting map, and how to choose. The family pages hold the depth — each row below links to the page that proves it.

Feature matrix

Features are graded as: full support, partial/emulated, optional, or absent.

CapabilitysystemdsysvinitOpenRCrunitdinit
Dependency graph at bootyesvia insserv headersyes (computed per run)noyes
Dependency semantics at runtimeyes (transactional)nopartial (stop ordering only)noyes (hard/milestone/soft)
Parallel startupyes, graph-drivenpartial (startpar batches)yes (rc_parallel)yes (all runsvs concurrent)yes, graph-driven
Service supervision / auto-restartyes (Restart=)nooptional (supervise-daemon)yes (always)yes (restart, smooth-recovery)
Socket activationyesnononopartial (socket listening)
On-demand activation (path/dbus)yesnononono
Timers / schedulingyes (.timer)no (use cron)no (use cron)no (use cron)no (use cron)
Per-service cgroupsyes (v1/v2)nooptional (v1/v2 integration)nopartial (newer versions)
Structured loggingyes (journald)no (syslog/bootlogd)no (syslog, rc.log)per-service files (svlogd)optional per-service logs
Runlevels / statestargets + compatrunlevels 0-6,Srunlevels (dirs)stages 1/2/3 + service setsprofiles (boot service + waits-for.d)
User session servicesyes (systemctl --user)nono (pair with elogind)yes (manual runsvdir)yes (user manager)
SysV init script compatyes (generator)nativeyes (scripts adapted)nono
Config languagedeclarative key=value unitsPOSIX shellshell + keywordsshell run scriptsdeclarative key=value
Footprintlarge (C, many binaries)smallmedium (shell-heavy)very small (static C)small (C++)
Live dependency evaluationyes (daemon-reload)noyes (recomputed)noyes
Default distrosmost mainstreamDevuan, Slackware, antiXGentoo, Alpine, ArtixVoidChimera, eweOS

Three structural observations the table hides:

  1. Supervision vs dependencies is the real axis. sysvinit and OpenRC start services; runit supervises them; systemd and dinit do both. That is why “my daemon crashed” has five different answers, from “nothing happens” (sysvinit) to “restarted within a second” (runit).
  2. The journal gap is bigger than it looks. Without journald you need syslog plus per-service conventions; runit is the only alternative that ships a real per-service logging story (svlogd) as part of its model.
  3. Compat layers matter for adoption. systemd’s sysv generator let distros switch while packages still shipped init scripts; OpenRC’s script shape is close enough to sysvinit that porting is mostly depend() syntax. runit and dinit require real rewrites, which is part of why their ecosystems stay smaller.

Architecture sketches

systemd (PID 1, resident manager)
  unit dependency graph ──> transaction engine ──> start units in parallel
        │                                              │
        │ ordering + requires                          │ every service in own cgroup
        ▼                                              ▼
  targets (multi-user, graphical)              supervise: Restart=, KillMode=

sysvinit (PID 1, stateless script runner)
  /etc/inittab ──> /etc/init.d/rc N ──> rcN.d/K* then S* in sequence
        │                                     (insserv .depend.* reorders,
        │ respawn entries only                 startpar runs batches parallel)
        ▼
  getty respawn; services = one-shot shell scripts

OpenRC (PID 1 = sysvinit/openrc-init; openrc-run = dependency engine)
  runlevel dirs (/etc/runlevels/default/*) ──> openrc-run solver
        │                                       │
        │ need/use/want/before/after            ▼
        ▼                                  shell scripts, optional supervise-daemon

runit (PID 1 = runit: stage 1/2/3 machine)
  stage 2: runsvdir /service ──> one runsv per dir ──> ./run (+ ./log/run)
                                     │ restarts forever, 1s backoff
                                     ▼
                               sv client sends control commands

dinit (PID 1 = dinit, resident manager)
  service descriptions (type=process/script/internal/triggered)
        │ depends-on / waits-for / milestone
        ▼
  dependency graph ──> start/stop with failure propagation ──> supervise

Command and concept porting map

Tasksystemdsysvinit (Debian)OpenRCrunitdinit
Start servicesystemctl start nginx/etc/init.d/nginx start (or service nginx start)rc-service nginx startsv up nginxdinitctl start nginx
Stop servicesystemctl stop nginx/etc/init.d/nginx stoprc-service nginx stopsv down nginxdinitctl stop nginx
Restartsystemctl restart nginx/etc/init.d/nginx restartrc-service nginx restartsv restart nginxdinitctl restart nginx
Statussystemctl status nginx/etc/init.d/nginx statusrc-service nginx statussv status nginxdinitctl status nginx
Enable at bootsystemctl enable nginxupdate-rc.d nginx defaultsrc-update add nginx defaultln -s /etc/sv/nginx /var/service/dinitctl enable nginx
Disable at bootsystemctl disable nginxupdate-rc.d nginx disablerc-update del nginx defaultrm /var/service/nginxdinitctl disable nginx
List servicessystemctl list-units --type=servicels /etc/rc2.d/rc-status --allsv status /var/service/*dinitctl list
Mask/blocksystemctl mask nginxchmod -x /etc/init.d/nginxrc-update del + remove scriptremove symlink (+ touch down)unload / remove description
Change system statesystemctl isolate multi-user.targettelinit 3openrc defaultrunsvchdir (service sets)switch boot profile
Reload config after editsystemctl daemon-reloadnothing to reloadnothing to reloadrescan within 5s (or restart runsvdir)dinitctl reload
Logsjournalctl -u nginx/var/log/syslog, bootlogdsyslog + /var/log/rc.log/var/log/nginx/current (svlogd)per-service logfile
“What state is the system in?”systemctl get-default / is-system-runningrunlevelrc-statussv status set / stagedinitctl status boot

History timeline

YearEvent
1983System V release defines runlevels and /etc/inittab
1990sLinux distros standardize on Miquel van Smoorenburg’s sysvinit
2001–2004daemontools lineage (DJB) inspires runit (Gerrit Pape) and eventually s6
2007–2008OpenRC extracted from Gentoo baselayout (Roy Marples)
2010systemd first release (Poettering, Sievers)
2011Fedora 15 ships systemd as default
2011Debian squeeze makes insserv dependency-based boot default
2012–2014Most major distros announce systemd defaults
2014Devuan forks Debian over the init decision
2014–2017Ubuntu returns from Upstart to systemd (16.04)
2015+Artix, Void and friends consolidate the “init-choice” distro niche
2017+dinit development; adopted by Chimera Linux and eweOS
2023sysvinit 3.x line (Jesse Smith) keeps traditional init maintained

Decision guide

  • You are studying for interviews: systemd + sysvinit deeply, one alternative shallowly. The boot-process, runlevels and service-management pages of those two families cover ~90% of questions asked.
  • You operate servers: systemd is the default almost everywhere; learn its debugging path (systemctl status → journalctl -u → show properties) until it is muscle memory. Know sysvinit concepts to read older documentation and interview questions.
  • You build containers: no init at all (one process + a zombie reaper like tini), or runit for multi-process images; OpenRC-in-container is an established pattern for chroot-heavy workflows.
  • You run embedded: busybox init for the smallest footprints, runit where supervision matters and bytes are less scarce.
  • You want init freedom on a distro: OpenRC is the most “mainstream compatible” choice (dependency-aware, script-based); runit the most minimal; dinit the most modern; sysvinit the most traditional.
  • You need one killer feature: supervision with zero config → runit; timers/journal/socket activation → systemd; scripts with dependencies → OpenRC; small binary with real dependency semantics → dinit.

Interview Questions

Q: Why did systemd replace sysvinit despite the controversy?

Because its capability gap became operationally decisive: parallel boot from a real dependency graph, reliable supervision via cgroups (no more pidfiles lying about dead daemons), socket activation enabling both parallelism and lazy start, unified structured logging, and resource control per service. Distributions weigh maintenance cost too: one resident manager replacing thousands of package-specific shell scripts reduces variant bugs. The controversy (scope creep, binary logs) is real but did not outweigh the capability case for mainstream distros.

Q: Your daemon dies on a sysvinit system and on a runit system. What happens on each?

On sysvinit: nothing automatic. PID 1 has already handed control to a shell script that exited; the service stays dead until a monitor (monit, cron, human) notices. On runit: the per-service runsv supervisor notices within moments and reruns ./run (with roughly a one-second pause between restarts), and the crash lands in that service’s svlogd log. This is the single most concrete illustration of the supervision axis.

Q: What does systemd’s SysV compatibility layer do, and what are its limits?

systemd-sysv-generator synthesizes transient service units from /etc/init.d scripts and their LSB headers at daemon-reload/boot, mapping Required/Should-Start into ordering and runlevel links into enablement. Limits follow from the model: the script still forks and daemonizes itself, so systemd supervises only the wrapper — no true Restart= semantics, status is what the script reports, and per-service cgroups only capture the script’s process tree imperfectly. It is a migration bridge, not a feature.

Q: Compare how the five systems express “start sshd after the network is up”.

sysvinit: static sequence numbers (S20sshd > S10network) or an LSB header Required-Start: $network consumed by insserv. OpenRC: depend() { need net; } (or use net for soft ordering). systemd: After=network-online.target plus Wants=network-online.target when actual reachability matters — ordering alone (After=network.target) does not wait for addresses. runit: no engine — a polling loop in the run script (sv check network || sleep 1; retry). dinit: depends-on: network (or milestone semantics for “network is up” checkpoints). Same sentence, five encodings — and the differences in failure semantics between them are exactly what interviewers probe.

Q: When would you deliberately choose NOT systemd on a general-purpose server?

When fleet uniformity is already non-systemd (Devuan/Alpine/Gentoo estates), when you need supervision more than ecosystem (runit on a minimal appliance gives you that in a few hundred kilobytes), when auditability of every boot step as plain shell is a requirement, or when the deployment is a container where systemd’s footprint buys nothing. The honest answer also lists what you re-add: syslog, cron, a supervisor or monitoring for non-supervised systems, and manual equivalents for socket activation and timers.

Key Takeaways

  • The five systems differ primarily on two axes: supervision (stateless scripts → resident managers) and dependency semantics (static numbers → live graphs); every other difference is downstream.
  • Command porting between systems is mechanical — the table above covers the daily 90%; the concepts behind the commands are what interviews test.
  • systemd’s ecosystem and sysvinit’s transparency are the two poles of the debate; OpenRC, runit and dinit each occupy a deliberate middle ground.

Cross-References

References