In this post, we’ll look at how runlevels work in two major init systems, systemd and OpenRC. If you’re interested in trying out systemd, I’d suggest using an Arch Linux Live ISO. For those interested in trying OpenRC, check out Funtoo. Both of these will work great in your favorite virtualization solution.


OpenRC uses runlevels in very much the same way as sysvinit (or BSD init). At any given time the system is in one of the defined runlevels, there are three internal runlevels and four user defined runlevels.

Internal Runlevels:

User Runlevels:

A system transitions between runlevels like so:

sysinit -> boot -> default -> shutdown

The services controlled by a particular runlevel are controlled by /etc/inittab and via the rc-update command.

On a modern Funtoo LXC Container, your runlevels might look like this:

± # rc-update -v
             bootmisc | boot
         busybox-ntpd |
     busybox-watchdog |
          consolefont |
                 dbus |
                devfs |                       sysinit
               dhcpcd |
                dmesg |                       sysinit
                 fsck | boot
           git-daemon |
                  gpm |
             hostname | boot
              hwclock | boot
             iptables |      default
              keymaps | boot
            killprocs |              shutdown
    kmod-static-nodes |                       sysinit
                local |      default
           localmount | boot
              modules | boot
             mount-ro |              shutdown
                 mtab | boot
               murmur |
           netif.eth0 |      default
             netif.lo |                       sysinit
           netif.tmpl |
             netmount |      default
                nginx |      default
              numlock |
              openvpn |      default
              pciparm |
              postfix |
               procfs | boot
            pydoc-2.7 |
            pydoc-3.3 |
            pydoc-3.4 |
                redis |
                 root | boot
               rsyncd |
            savecache |              shutdown
                 sshd |      default
                 swap | boot
            swapfiles | boot
              swclock |
               sysctl | boot
                sysfs |                       sysinit
         termencoding | boot |                       sysinit
       tmpfiles.setup | boot
                 udev |                       sysinit
           udev-mount |                       sysinit
       udev-postmount | boot
              urandom | boot

Services can be added to runlevels with:

rc-update add <service> <runlevel>

Or removed with:

rc-update del <service> <runlevel>

Changing between runlevels is done via the rc <runlevel> command, but most users will never need to do this as runlevels are (mostly) managed by the system.

In the case where a user does want to change runlevels in this way, it is often such that they are utilizing Stacked Runlevels.

For example, if a user wanted to differentiate which services were running on their laptop when it was powered or on battery, they could create two separate runlevels stacked atop default. While plugged in, the laptop could be running test databases or any number of services, but after changing runlevels these services could be disabled to save on battery.




systemd uses targets instead of runlevels. Each target has a unique name, and multiple targets can be active at one time. Since most distributions using systemd have migrated from a runlevel based init system, there are targets which roughly correspond to the runlevels used by OpenRC, init, and sysvinit.

It’s important to understand that these are not necessarily the only active targets. In the case of the graphical target, a display manager service like kdm might be started, this target would also activate the multi-user target.

The multi-user target might also activate a service like dbus, and another target named basic. This is interesting because it allows us to cleanly compose and extend the system to suit our needs.

A .target file resides in /usr/lib/systemd/system or /usr/lib/systemd/system/ and looks like this:

Description=Graphical Interface

This file would be accompanied by a <target>.wants directory containing the services it should enable.

Targets can be controlled very similar to systemd’s .service unit files, you just need to add .target at the end.

systemctl status
± % systemctl status - Graphical Interface
Loaded: loaded (/lib/systemd/system/; enabled)
Active: active since Thu 2014-09-25 21:21:26 PDT; 4 days ago
Docs: man:systemd.special(7)

 Sep 25 21:21:26 aluminum systemd[1]: Starting Graphical Interface.
 Sep 25 21:21:26 aluminum systemd[1]: Reached target Graphical Interface.

The .target based approach allows for various services to be grouped in a logical manner. For example, is is the state of targets on a modern Fedora Desktop:

± % systemctl list-units --type=target --all
UNIT                   LOAD      ACTIVE   SUB    DESCRIPTION           loaded    active   active Basic System       loaded    active   active Bluetooth  loaded    inactive dead   Encrypted Volumes (Pre)      loaded    active   active Encrypted Volumes       loaded    inactive dead   Emergency Mode           loaded    inactive dead   Final Step           loaded    active   active Login Prompts       loaded    active   active Graphical Interface    loaded    active   active Local File Systems (Pre)        loaded    active   active Local File Systems      loaded    active   active Multi-User System  loaded    inactive dead   Network is Online         loaded    active   active Network             loaded    active   active Network File System Server loaded    inactive dead   User and Group Name Lookups           loaded    active   active Paths   loaded    inactive dead   Remote File Systems (Pre)       loaded    active   active Remote File Systems          loaded    inactive dead   Rescue Mode        loaded    inactive dead   Shutdown           loaded    inactive dead   Sleep          loaded    active   active Slices         loaded    active   active Sockets           loaded    active   active Sound Card         loaded    active   active Suspend            loaded    active   active Swap         loaded    active   active System Initialization          not-found inactive dead       loaded    inactive dead   System Time Synchronized          loaded    active   active Timers          loaded    inactive dead   Unmount All Filesystems

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.

31 loaded units listed.
To show all installed unit files use 'systemctl list-unit-files'.



Further Reading