--- title: "systemd vs SysVinit vs OpenRC: Linux Init Systems Compared" description: "Compare systemd, SysVinit and OpenRC: how each boots Linux, manages services, logs and dependencies, with the same service written for all three init systems." category: "Comparison Guide" language: en source: https://cubepath.com/docs/comparison-guide/systemd-vs-sysvinit-vs-openrc updated: 2026-09-25 --- # systemd vs SysVinit vs OpenRC: Linux Init Systems Compared The init system is the first process the kernel starts (PID 1). It brings up the rest of the system, starts and stops services, and reaps orphaned processes until shutdown. Most mainstream distributions use systemd today, but SysVinit and OpenRC are still in active use on Devuan, Alpine, Gentoo and embedded systems. This guide compares the three on design, day-to-day commands, logging, dependencies and resource control, and shows the same service written for each one so you can judge the differences yourself. ## Which init system does your server use? Before comparing, check what is running as PID 1 on your machine: ```bash ps -p 1 -o comm= ``` ```output systemd ``` On a SysVinit system this prints `init`. On OpenRC systems PID 1 is usually also `init` (SysVinit's or BusyBox's), because OpenRC is a service manager that runs on top of an existing init binary. You can confirm OpenRC by checking for its script runner: ```bash command -v openrc-run ``` ```output /sbin/openrc-run ``` ## At a glance | | SysVinit | systemd | OpenRC | |---|---|---|---| | Service definition | Shell scripts in `/etc/init.d/` | Declarative unit files (`.service`, `.timer`, `.socket`...) | Shell scripts using `#!/sbin/openrc-run` with helper variables | | Startup ordering | Numbered symlinks per runlevel (`S01`, `S02`...) or LSB header dependencies | Dependency graph (`Requires=`, `Wants=`, `After=`), parallel by default | `depend()` function (`need`, `use`, `after`), optional parallel start | | Process supervision | None for services (only `respawn` entries in `/etc/inittab`) | Built in (`Restart=on-failure`) | Optional via `supervise-daemon` | | Logging | Whatever the daemon writes, usually syslog | journald (binary journal, `journalctl`) plus optional syslog forwarding | Syslog (for example BusyBox syslogd on Alpine) | | Resource limits | `ulimit` inside the script | cgroups v2 per unit (`MemoryMax=`, `CPUQuota=`) | cgroups through `rc_cgroup_*` settings | | Scheduled jobs | cron | systemd timers or cron | cron | | Typical distributions | Devuan, Slackware, older Debian/RHEL, embedded | Ubuntu, Debian, RHEL, Rocky, Fedora, Arch, SUSE | Alpine, Gentoo, Artix, Devuan (optional) | ## SysVinit SysVinit reads `/etc/inittab`, enters a runlevel (commonly 2, 3 or 5 for multi-user, 0 for halt and 6 for reboot) and runs the scripts linked into `/etc/rcN.d/`. Scripts whose names start with `S` are called with `start`, those starting with `K` with `stop`, in alphabetical order. Each script is a plain shell program that must handle `start`, `stop`, `restart` and `status` itself. Strengths: - Very small and easy to reason about: everything is a shell script you can read and run by hand. - No dependencies beyond a shell, which suits tiny embedded images. Weaknesses: - Services start one after another, so boot is slower on systems with many services. - No supervision: if a daemon crashes it stays down until someone restarts it. - PID file handling and daemonization are reimplemented, often badly, in every script. Daily commands on a Debian-style SysVinit system: ```bash sudo service nginx start sudo service nginx status sudo update-rc.d nginx defaults sudo update-rc.d nginx disable ``` RHEL-style systems before version 7 used `chkconfig nginx on` instead of `update-rc.d`. ## systemd systemd treats everything it manages as a unit: services, mount points, sockets, timers and targets (the equivalent of runlevels, such as `multi-user.target`). It builds a dependency graph from the unit files and starts everything it can in parallel. Each service runs in its own cgroup, so systemd always knows which processes belong to it, can restart it on failure and can apply CPU, memory and I/O limits. Strengths: - Declarative unit files, so there is no shell boilerplate to get wrong. - Supervision, restart policies, sandboxing (`ProtectSystem=`, `PrivateTmp=`) and resource control built in. - Structured logging with `journalctl`, filtered by unit, boot, priority or time. - Timers, socket activation and boot analysis tools in the same toolset. Weaknesses: - Large scope: it also covers logging, networking (`systemd-networkd`), DNS (`systemd-resolved`) and more, which some administrators consider too much for PID 1. - Linux only and tied to glibc, so it is not an option on musl-based distributions such as Alpine. Daily commands: ```bash sudo systemctl start nginx sudo systemctl status nginx sudo systemctl enable --now nginx sudo systemctl disable nginx sudo systemctl reload nginx ``` ## OpenRC OpenRC is a dependency-based service manager originally written for Gentoo. It keeps the familiar `/etc/init.d/` scripts but replaces the boilerplate with a small framework: the script sets variables such as `command` and `pidfile`, declares dependencies in a `depend()` function, and `openrc-run` does the rest. Runlevels are named directories (`boot`, `default`, `shutdown`) under `/etc/runlevels/`. Strengths: - Much shorter scripts than SysVinit, with real dependency resolution. - Small and portable: it runs on Linux with glibc or musl and on BSDs. - Optional supervision with `supervise-daemon`, and optional parallel startup. Weaknesses: - Smaller ecosystem: upstream projects ship systemd units far more often than OpenRC scripts, so you may have to write your own. - No built-in structured logging or timers. Daily commands on Alpine or Gentoo: ```bash sudo rc-service nginx start sudo rc-service nginx status sudo rc-update add nginx default sudo rc-update del nginx default rc-status ``` ## The same service written for each init system The clearest way to see the difference is to define one service three times. Assume a program at `/usr/local/bin/myapp` that runs in the foreground, listens on port 8080 and should run as the unprivileged user `myapp`. ### systemd unit Create the unit file: ```bash sudo nano /etc/systemd/system/myapp.service ``` ```ini [Unit] Description=My application After=network-online.target Wants=network-online.target [Service] User=myapp Group=myapp ExecStart=/usr/local/bin/myapp --port 8080 Restart=on-failure RestartSec=5 MemoryMax=512M CPUQuota=50% [Install] WantedBy=multi-user.target ``` Load and start it: ```bash sudo systemctl daemon-reload sudo systemctl enable --now myapp ``` systemd handles the user switch, restarts the process if it exits with an error, and enforces a 512 MB memory ceiling and half a CPU through cgroups. ### OpenRC script ```bash sudo nano /etc/init.d/myapp ``` ```sh #!/sbin/openrc-run name="myapp" description="My application" command="/usr/local/bin/myapp" command_args="--port 8080" command_user="myapp:myapp" supervisor="supervise-daemon" pidfile="/run/${RC_SVCNAME}.pid" depend() { need net after firewall } ``` ```bash sudo chmod +x /etc/init.d/myapp sudo rc-update add myapp default sudo rc-service myapp start ``` With `supervisor="supervise-daemon"`, OpenRC keeps the process in the foreground and restarts it if it dies. Without that line it falls back to `start-stop-daemon` and no supervision. ### SysVinit script A SysVinit script has to do everything itself. On Debian-family systems the LSB header lets `update-rc.d` compute the start order: ```bash sudo nano /etc/init.d/myapp ``` ```sh #!/bin/sh ### BEGIN INIT INFO # Provides: myapp # Required-Start: $network $remote_fs $syslog # Required-Stop: $network $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: My application ### END INIT INFO DAEMON=/usr/local/bin/myapp PIDFILE=/run/myapp.pid . /lib/lsb/init-functions case "$1" in start) log_daemon_msg "Starting myapp" "myapp" start-stop-daemon --start --background --make-pidfile --pidfile "$PIDFILE" \ --chuid myapp:myapp --exec "$DAEMON" -- --port 8080 log_end_msg $? ;; stop) log_daemon_msg "Stopping myapp" "myapp" start-stop-daemon --stop --pidfile "$PIDFILE" --retry 10 log_end_msg $? rm -f "$PIDFILE" ;; restart) "$0" stop "$0" start ;; status) status_of_proc -p "$PIDFILE" "$DAEMON" myapp ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 ;; esac ``` ```bash sudo chmod +x /etc/init.d/myapp sudo update-rc.d myapp defaults sudo service myapp start ``` There is no restart on crash and no memory limit. To add either you would need an external supervisor or `ulimit` calls in the script. ## Logging This is where daily work differs most. With systemd, every unit's stdout and stderr goes to the journal automatically: ```bash journalctl -u myapp -f journalctl -u myapp --since "1 hour ago" journalctl -b -p err ``` The last command shows only messages of priority `err` or worse since the current boot. On Ubuntu and Debian, journald also forwards to rsyslog when it is installed, so `/var/log/syslog` keeps working. With SysVinit and OpenRC, logging is whatever the daemon does. Most daemons write to syslog, so you read `/var/log/syslog`, `/var/log/messages` (Alpine with BusyBox syslogd) or the program's own log file: ```bash tail -f /var/log/messages ``` ## Dependencies and boot order - **SysVinit** orders scripts by the numbers in their symlink names. The LSB header (`Required-Start:`) lets tools such as `insserv` compute those numbers, but it is still a linear sequence. - **OpenRC** resolves `need` (hard dependency), `use` (soft dependency, start it if it is enabled) and `after`/`before` (ordering only). With `rc_parallel="YES"` in `/etc/rc.conf` it starts independent services at the same time; it is off by default because parallel output is harder to read. - **systemd** separates requirement (`Requires=`, `Wants=`) from ordering (`After=`, `Before=`). A common mistake is writing `After=postgresql.service` without `Wants=` or `Requires=`, which orders the units but does not start PostgreSQL. To see what slows down a systemd boot: ```bash systemd-analyze systemd-analyze blame | head systemd-analyze critical-chain ``` ```output Startup finished in 2.108s (kernel) + 6.472s (userspace) = 8.580s graphical.target reached after 6.431s in userspace. ``` OpenRC and SysVinit have no equivalent tool. You measure boot time with timestamps in the logs or with `bootchart`. ## Resource control and scheduling systemd places every service in a cgroup and exposes the limits directly in the unit: `MemoryMax=`, `CPUQuota=`, `IOWeight=`, `TasksMax=`. You can change them live: ```bash sudo systemctl set-property myapp.service MemoryMax=1G systemd-cgtop ``` OpenRC can also place services in cgroups through the `rc_cgroup_*` settings documented in `/etc/rc.conf`, but the configuration is less granular. SysVinit has no cgroup support; you are limited to `ulimit` inside the script. For scheduled jobs, systemd offers timers as an alternative to cron, with logging in the journal and `Persistent=true` to catch up on runs missed while the machine was off: ```bash systemctl list-timers ``` On SysVinit and OpenRC systems you use cron. ## Troubleshooting commands side by side | Task | SysVinit | systemd | OpenRC | |---|---|---|---| | Service status | `service myapp status` | `systemctl status myapp` | `rc-service myapp status` | | Failed services | Check logs manually | `systemctl --failed` | `rc-status --crashed` | | Services enabled at boot | `ls /etc/rc2.d/` | `systemctl list-unit-files --state=enabled` | `rc-update show` | | Debug a start script | `sudo sh -x /etc/init.d/myapp start` | `journalctl -u myapp -b` | `sudo /etc/init.d/myapp -d start` | | Check definition syntax | `sh -n /etc/init.d/myapp` | `systemd-analyze verify myapp.service` | `sh -n /etc/init.d/myapp` | | Current runlevel/target | `runlevel` | `systemctl get-default` | `rc-status --runlevel` | ## Migrating between init systems - **SysVinit to systemd:** systemd runs old scripts from `/etc/init.d/` through `systemd-sysv-generator`, so a missing unit file does not stop a service from working. Still, rewrite important services as native units to get supervision, cgroup limits and clean logging. Move the `ExecStart=` command line out of the script, drop PID file handling and let the program run in the foreground. - **SysVinit to OpenRC:** replace the `case` block with `command`, `command_args` and `pidfile` variables and translate the LSB header into a `depend()` function. Devuan supports both, so you can switch per system. - **systemd to OpenRC:** usually means changing distribution (for example to Alpine or Artix). Translate `ExecStart=` into `command`/`command_args`, `User=` into `command_user`, `Restart=` into `supervisor="supervise-daemon"` and replace timers with cron entries. Sandboxing options such as `ProtectSystem=` have no direct equivalent. ## Which one should you choose? In practice the init system comes with the distribution, so the real decision is which distribution fits the job. **Choose systemd** (Ubuntu, Debian, Rocky Linux) for general-purpose servers. It is what upstream software, documentation and configuration management tools target, and supervision, resource limits and the journal save real time in operations. **Choose OpenRC** (Alpine, Gentoo) when you want a small, readable system, need musl or BSD compatibility, or build minimal images and appliances. Alpine with OpenRC is a common choice for lightweight VMs and routers. **Choose SysVinit** only for legacy systems, very constrained embedded devices or when you deliberately run Devuan or Slackware. For new deployments it offers nothing that OpenRC does not do better. ## Conclusion SysVinit, systemd and OpenRC all start services at boot, but they differ in how much they do for you. systemd gives supervision, dependency resolution, logging and cgroup limits in one integrated toolset, OpenRC keeps shell scripts but removes most of their boilerplate, and SysVinit leaves everything to the script author. As next steps, write a hardened systemd unit for one of your own services with `systemd-analyze security`, replace a cron job with a systemd timer, or try Alpine Linux in a test VPS to get familiar with OpenRC.