Runlevels and /etc/rc*.d/

The core idea of sysvinit is something called runlevels, which are essentially just a way of organizing a collection of init scripts which have to run when the system starts or shuts down. Each runlevel corresponds to a directory in /etc/, which in turn contains symlinks to scripts in /etc/init.d/.

For historical reasons, there are a lot of runlevels, but a bunch of them aren't really used for anything special on Debian systems. You can read the full details of this in the Debian manual, but the short version is that the system normally boots to runlevel 2, which is multi-user mode. Adapted from the manual, here's a partial table of levels:





System boot.



Halt the system.



Single user mode (if you switch from multi-user mode).



Multi-user mode.

3 - 5

/etc/rc3.d through /etc/rc5.d/

Identical to runlevel 2 (unless you do something funny with your system).



Reboot the system.

Let's get a list of what's in runlevel 2:

cd /etc/rc2.d/
ls -l

ls -l gives a long listing of files, which will helpfully show you if a file is really a link to another file. Again, all of the actual init scripts turn out to live in /etc/init.d.

If a file in /etc/rc*.d/ starts with K, it will be invoked by the init system with [name of script] stop (K is for kill), and if it starts with S, it will be invoked with [name of script] start.

Init scripts usually accept (most of) the following command-line parameters:

  • start - start the service
  • stop - stop the service
  • status - check whether the service is running
  • reload - have the service reload configuration files
  • restart - stop and start the service

So, for example, you could say /etc/init.d/ssh restart to stop and start the SSH service. You often see commands like this in tutorials and HOWTOs.

The SSH Init Script

As an example, load up /etc/init.d/ssh in a text editor and give it a look.

cd /etc/init.d
nano ssh

This is actually quite a few lines of code for what seems like a pretty simple task - it just needs to start or stop a program, right? It turns out, though, that there can be a lot of considerations involved in doing that. For example, these rather squirrelly-looking lines...

test -x /usr/sbin/sshd || exit 0
( /usr/sbin/sshd -\? 2>&1 | grep -q OpenSSH ) 2>/dev/null || exit 0

...make sure that sshd exists, is executable, and at least claims to be the SSH daemon provided by the OpenSSH project. The rest of the script defines functions for all sorts of housekeeping and sanity checks before it gets to the part where it handles the start/stop/etc. commands in a case statement:

case "$1" in
        log_daemon_msg "Starting OpenBSD Secure Shell server" "sshd" || true
        if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/ --exec /usr/sbin/sshd -- $SSHD_OPTS; then
            log_end_msg 0 || true
            log_end_msg 1 || true
        log_daemon_msg "Stopping OpenBSD Secure Shell server" "sshd" || true
        if start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/; then
            log_end_msg 0 || true
            log_end_msg 1 || true
   # a whole bunch of other stuff happens here


For one-off projects on the Pi, you almost certainly don't need to exercise this level of caution, but you will want to handle the common commands.

It would be a pain to write a lot of this stuff out from scratch. Fortunately, we have /etc/init.d/skeleton to work with. This just provides the "bones" of an init script, and should work with a few minor changes. Next, we'll look at adapting it to run our example

This guide was first published on Sep 01, 2015. It was last updated on Sep 01, 2015.

This page (SysV init: Runlevels) was last updated on May 10, 2021.

Text editor powered by tinymce.