Skip to content

OpenRC and other init systems

On most of the distributions this knowledge base covers, you manage services with systemctl. On Gentoo you'll more often use rc-service and rc-update, because Gentoo defaults to OpenRC. The commands differ, but the larger difference is in how each system is designed. That determines how services are defined, how failures are detected, where logs go, and how much of the system the init software controls.

This page explains those design choices. Command-by-command equivalents belong in reference material; here the goal is to understand why the systems behave differently.

What "init" actually means

When the kernel finishes booting, it starts one userspace program as process ID 1 (PID 1). Everything else on the system descends from it. Discussions of "init systems" usually mix together four separate jobs:

Job What it involves
PID 1 Being the first process; adopting and reaping orphaned processes so they don't linger as zombies; never exiting (if PID 1 dies, the kernel panics); starting shutdown or reboot.
Service manager Deciding which services to start, in what order, based on dependencies, and stopping them cleanly.
Process supervisor Watching running daemons and restarting them when they die.
Surrounding plumbing Logging, mounting filesystems, device events, user sessions, timers, network setup.

Init systems differ mainly in which of these jobs they take on, and whether they combine them in one project. That one idea explains most of the comparison below:

  • SysV init is a minimal PID 1 plus a set of shell-script conventions.
  • OpenRC is primarily a service manager. It can run on top of another PID 1 and supervises only if you ask it to.
  • runit, s6 and dinit are built around supervision, with dinit and s6-rc adding dependency management.
  • systemd combines all four, plus much of the surrounding plumbing.
flowchart TB
    subgraph SysV["SysV init"]
        direction TB
        s1["PID 1: /sbin/init<br/>(reads /etc/inittab)"] --> s2["rc scripts in /etc/init.d<br/>run in a fixed order"]
    end
    subgraph OpenRC["OpenRC (Gentoo, Alpine)"]
        direction TB
        o1["PID 1: sysvinit, BusyBox init<br/>or openrc-init"] --> o2["OpenRC service manager<br/>(dependency resolution)"]
        o2 --> o3["optional supervisor:<br/>supervise-daemon or s6"]
    end
    subgraph systemd["systemd"]
        direction TB
        d1["PID 1 = service manager = supervisor<br/>(units, cgroups, jobs)"] --> d2["journald, logind, udevd,<br/>networkd, resolved, timers…"]
    end

A short history

SysV init (from AT&T UNIX System V, standard on Linux through the 2000s) defined numbered runlevels (0 = halt, 1 = single user, 3 = multi-user, 5 = graphical, 6 = reboot). Each runlevel had a directory of symlinks named like S20ssh and K80ssh, pointing to shell scripts in /etc/init.d/. The numbers set the start (S) and kill (K) order. This was simple and transparent, but the ordering was a global sequence of numbers, not real dependencies. Services started one at a time, and once a daemon had backgrounded itself, nothing watched it.

Several projects tried to fix these weaknesses in different ways:

  • Gentoo's rc scripts added named dependencies to init scripts. Starting around 2007, Roy Marples reworked them into a standalone, portable project: OpenRC. It became Gentoo's stable default around 2011, and is also the default on Alpine Linux.
  • Upstart (Ubuntu, 2006) made boot event-driven. It was used by Ubuntu and RHEL 6 before being replaced by systemd.
  • runit (Gerrit Pape) and Daniel J. Bernstein's earlier daemontools focused on reliable supervision: each service runs in the foreground under a small supervisor that restarts it if it exits.
  • systemd (Lennart Poettering and Kay Sievers, first released in 2010) took ideas from Apple's launchd and socket activation, and integrated service management with Linux-specific kernel features, especially control groups (cgroups). Fedora adopted it in 2011, RHEL 7 in 2014, and Debian 8 and Ubuntu 15.04 in 2015. It is now the default on Debian, Ubuntu, RHEL and its derivatives, Fedora and Arch Linux.

Later projects such as s6 (Laurent Bercot) and dinit (Davin McCall; the default on Chimera Linux) continue the supervision-first approach, adding dependency management and readiness notification.

How OpenRC works

OpenRC is a service manager, not necessarily PID 1

Historically, OpenRC doesn't replace PID 1. On Gentoo it traditionally runs under sysvinit: /etc/inittab tells sysvinit to call openrc with a runlevel name. On Alpine, BusyBox init is PID 1 and calls OpenRC in the same way. Newer OpenRC releases also ship openrc-init, a small PID 1 of its own, but the design still keeps "being PID 1" and "managing services" as separate concerns.

This separation is deliberate. It keeps the most critical process (PID 1) small, and it lets OpenRC run on different PID 1 programs and even on other operating systems; OpenRC has supported FreeBSD and NetBSD.

Named runlevels

OpenRC keeps the runlevel idea but uses names instead of numbers. Each runlevel is a directory of symlinks in /etc/runlevels/:

  • sysinit: very early system setup (/proc, /sys, /dev)
  • boot: things every boot needs (hostname, filesystems checked and mounted, clock, kernel modules)
  • default: normal operation (e.g. sshd, nginx, chronyd)
  • shutdown: the shutdown sequence

You can create your own runlevels (for example a maintenance runlevel with networking but without application services) and switch between them. rc-update add sshd default simply creates the symlink /etc/runlevels/default/sshd.

Unlike SysV, membership of a runlevel says what should run, not in what order. Order comes from dependencies.

Service scripts with declared dependencies

An OpenRC service is a script in /etc/init.d/ run by openrc-run. It's still a shell script, but most of the logic comes from shared OpenRC functions, so a typical modern script is short and largely declarative:

#!/sbin/openrc-run
# /etc/init.d/exampled

description="Example daemon"
command="/usr/sbin/exampled"
command_args="${EXAMPLED_OPTS}"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid"

depend() {
    need net
    use logger dns
    after firewall
}

The depend() function separates two ideas that SysV merged into one number:

  • Requirement strength: need (hard: start it, and fail without it), want (start it if possible), use (use it if it's already in the runlevel).
  • Ordering: after and before, which don't pull services in.

There is also provide, for virtual services: several services can provide logger or net, and a dependent service doesn't need to know which one is installed. Settings that belong to the administrator rather than the package go in /etc/conf.d/<service>, and system-wide behaviour goes in /etc/rc.conf.

Scripts can still define their own start(), stop() and extra commands (for example reload or checkconfig), because they are shell scripts. That flexibility is one of OpenRC's main attractions, and it's also where inconsistency can creep in.

Tracking and supervising daemons

By default, OpenRC starts a daemon with start-stop-daemon and then tracks it by PID file. If the daemon later crashes, OpenRC by default doesn't notice: rc-status may still show it as started (or as crashed, if the check finds the process has gone), but nothing restarts it. This is the classic SysV-era behaviour.

OpenRC offers two ways to get real supervision, chosen per service:

  • supervise-daemon, OpenRC's own supervisor, enabled with supervisor=supervise-daemon in the service script or its conf.d file. It keeps the daemon in the foreground as its child and restarts it according to respawn_* settings.
  • Integration with s6, for those who want a dedicated supervision suite.

On Linux, OpenRC can also put each service into its own cgroup (rc_cgroup_mode in /etc/rc.conf). This means stopping a service can reliably kill every process it created, even ones that escaped the PID file. The same problem is what pushed systemd to build on cgroups from the start.

Parallel startup

OpenRC can start independent services in parallel (rc_parallel="YES"), but it's off by default. The OpenRC documentation warns that console output gets mixed up and some edge cases are less tested. Serial startup with dependency ordering is the conservative default, in keeping with the project's focus on predictability.

Logging

OpenRC doesn't include a logging system. Daemons log as they always have, to syslog (provided by a separate package such as sysklogd, metalog, syslog-ng or rsyslog) or to their own files. supervise-daemon can redirect a service's output to files. There is no equivalent of the systemd journal unless you install a separate logger.

How systemd differs

systemd starts from a different premise: the init system should know everything about the services it runs, and should use Linux kernel features to do so.

  • Declarative units instead of scripts. A service is an INI-style unit file ([Unit], [Service], [Install]), not a program. Behaviour that OpenRC scripts implement in shell (users, environment, restart policy, sandboxing, resource limits) is expressed as directives like User=, Restart=on-failure, ProtectSystem=strict or MemoryMax=. Unit files are easier to analyse and harder to get subtly wrong, but you can only do what a directive exists for.
  • Many unit types. Besides .service there are .socket, .timer, .mount, .path, .target and others, so the init system takes over jobs that used to belong to inetd, cron and fstab handling.
  • Dependencies and ordering, as in OpenRC, but more detailed. Requires=/Wants= say what is pulled in; After=/Before= say the order. The two are separate in systemd too. The main conceptual overlap with OpenRC is this split; systemd adds a transactional "job" engine that computes the whole set of changes before applying them.
  • Targets instead of runlevels. multi-user.target and graphical.target play the part of runlevels, but they're just units that group other units, and several can be active at once.
  • Every service in its own cgroup, always. systemd always knows exactly which processes belong to which service. PID files are unnecessary and "the daemon forked and escaped" doesn't happen.
  • Supervision built in. Restart policies, watchdogs and readiness notification (Type=notify) are part of PID 1.
  • Parallel by default, helped by socket activation: systemd can create a service's listening socket before the service starts, so clients can connect early and dependent services don't need to wait.
  • An integrated suite. journald (structured, indexed logs), logind (sessions), udevd (devices), and optionally networkd, resolved, timesyncd and more, all developed together.

Supervision-first alternatives

System Core idea Where you'll meet it
runit Three-stage design: one-time boot tasks, then runsvdir supervises a directory of services, then shutdown. A service is a directory with a run script that runs the daemon in the foreground; sv controls it; svlogd handles logs. Very small, but no dependency management beyond what the run scripts do. Void Linux (default), Artix Linux (option)
s6 (+ s6-rc, s6-linux-init) A set of small, composable programs: a supervision tree (s6-svscan, s6-supervise), a dependency-based service manager (s6-rc) and an init (s6-linux-init). Strong on readiness notification and correctness. Artix Linux (option); as a supervisor under OpenRC
dinit One service manager plus supervisor, with dependencies and declarative service descriptions, intended as a middle ground between runit's minimalism and systemd's scope. Can run as PID 1 or as a user service manager. Chimera Linux (default), Artix Linux (option)
BusyBox init A tiny SysV-style PID 1 in a single binary, usually paired with OpenRC or simple scripts. Alpine Linux, embedded systems

Comparing the approaches

SysV init OpenRC systemd runit / s6 / dinit
Scope PID 1 + script conventions Service manager (PID 1 optional) PID 1, service manager, supervisor and a wide suite Supervisor first; s6-rc and dinit add dependencies
Service definition Shell scripts Shell scripts using shared functions; mostly declarative variables Declarative unit files Small run scripts (runit, s6) or declarative files (dinit)
Ordering Numbered sequence Named dependencies (need, use, want, after, before, provide) Named dependencies and ordering; transactional jobs None (runit); dependency graph (s6-rc, dinit)
Process tracking PID files PID files, supervisor, optional cgroups cgroups, always Supervisor, always
Restart on crash No (except respawn in inittab) Opt-in (supervise-daemon, s6) Built in (Restart=) Built in, the core purpose
Parallel start No Opt-in Yes, plus socket activation Yes
Logging syslog syslog or per-service files journald (optionally forwarding to syslog) Per-service loggers (svlogd, s6-log) or syslog
Portability Unix-wide Linux and BSDs Linux only Linux and others (runit, s6)

The trade-offs

Each design trades one property for another.

Transparency vs integration. OpenRC's scripts are plain shell, so you can read exactly what a service does, run it with sh -x, and change its behaviour with ordinary shell. systemd's units are less flexible, but because systemd understands them rather than just executing them, it can sandbox, account for, restart and analyse services in ways a script-based system can only approximate.

Small, separable parts vs one coordinated whole. OpenRC, runit and s6 follow a Unix tradition: small tools with narrow responsibilities that you combine yourself, a small PID 1, and the freedom to replace any part. systemd's components are developed together and assume each other, which gives consistency (one logging system, one way to express dependencies, one set of tools on every systemd distribution). The cost is that adopting systemd means adopting much of the stack, and it ties you to Linux and its kernel features.

Conservative defaults vs maximum use of the kernel. OpenRC defaults to serial startup and PID-file tracking, with supervision, parallelism and cgroups as options. systemd always uses cgroups, parallelism and supervision. On servers, boot speed matters less than predictability and recovery, which narrows the practical gap. The remaining differences are mostly about restart behaviour and observability.

Ecosystem gravity. Most Linux software now ships systemd unit files upstream, and some desktop components assume systemd's logind (alternatives such as elogind exist for non-systemd systems). On OpenRC systems, the distribution often provides the service scripts. Gentoo and Alpine maintain them carefully, but the work exists. For the four distribution families covered here, that means systemd knowledge transfers across Debian/Ubuntu, the RHEL family and Arch, while OpenRC knowledge mainly applies to Gentoo (and Alpine).

Why Gentoo defaults to OpenRC

OpenRC grew out of Gentoo's own rc system, so the defaults reflect Gentoo's values: administrator choice, small composable parts, and being able to see and change how the system works. Gentoo also supports systemd fully through systemd profiles. A Gentoo system with a systemd profile manages services like any other systemd distribution; the differences from Debian or Arch are in packaging, not in init.

In practice, the parts of administration that change between OpenRC and systemd are:

  • Enabling and controlling services: runlevel membership (rc-update) and rc-service instead of systemctl enable and systemctl start.
  • Writing services: an openrc-run script with depend() instead of a unit file.
  • Crash recovery: restarts only if you configure a supervisor.
  • Logs: a syslog daemon and files in /var/log instead of journalctl.
  • Timers and scheduled jobs: cron (e.g. cronie) instead of .timer units.
  • Boot problems: OpenRC's runlevels (sysinit, boot, default) instead of targets; see How to boot into rescue mode.

Most other administration (networking tools, storage, packages, firewalls) is unaffected by the choice of init system.

Further reading