sysvinit: Explanation & Insights

The original Linux init — a folder of shell scripts run one by one, simple to read and slow to boot.

What It Is

sysvinit — short for System V init, after the AT&T Unix release whose design it copies — is the classic init system that ran on nearly every Linux server for the first two decades of the operating system's life. Like any init system, it is the first userspace program the kernel starts, the one that gets PID 1, and the parent of every other process on the box. If you log into an old server and ps -p 1 -o comm= prints plain init, you're almost certainly looking at sysvinit.

What makes sysvinit sysvinit — as opposed to its successors upstart and systemd — is how it does the job. Its whole model rests on two ideas: runlevels (numbered modes the machine can be in — single-user maintenance, full multi-user networking, graphical desktop, halt, reboot) and a directory of shell scripts that start and stop each service. To boot, sysvinit reads which runlevel to enter, then runs the right scripts in order. That's essentially the entire architecture, and its great virtue is that you can read all of it — every script is plain shell you can open in a text editor and follow line by line.

This page walks through that model from zero: how PID 1 gets going, what lives in /etc/init.d and the /etc/rcN.d symlink farms, how the service command drives it, and — the honest part — why something so legible got replaced anyway.

How It Boots

The sequence is worth seeing, because once you've seen it the old documentation stops being cryptic.

  1. The kernel finishes loading and starts /sbin/init as PID 1.
  2. init reads its one config file, /etc/inittab, which names the default runlevel — the state the box should end up in. A line like id:3:initdefault: means "boot to runlevel 3" (full multi-user, networked, no GUI).
  3. init runs the system-init script, then enters the chosen runlevel by running every start script for that level.

Those per-runlevel start scripts live in directories named /etc/rc0.d, /etc/rc1.d, … /etc/rc6.d — one per runlevel. But the directories don't hold the real scripts; they hold symbolic links back to the actual scripts in /etc/init.d. This "symlink farm" is the heart of sysvinit and worth a proper look:

ls -l /etc/rc3.d/
lrwxrwxrwx 1 root root  17 Jan 10  2019 K01nginx     -> ../init.d/nginx
lrwxrwxrwx 1 root root  20 Jan 10  2019 S01rsyslog   -> ../init.d/rsyslog
lrwxrwxrwx 1 root root  17 Jan 10  2019 S20mysql     -> ../init.d/mysql
lrwxrwxrwx 1 root root  18 Jan 10  2019 S30apache2   -> ../init.d/apache2

Read the link names like a recipe. The leading letter is the verb: S means "start this when entering the runlevel," K means "kill (stop) this when leaving it." The two digits are the running order — sysvinit processes them in plain ascending sort, so S01 runs before S20 runs before S30. The rest is the script name. So on entering runlevel 3, this box starts rsyslog, then mysql, then apache2, in that order, and (per the K01nginx link) makes sure nginx is stopped.

That's the dependency model in full: two digits in a filename. There's no graph, no "apache needs mysql" declared anywhere — just a human who picked S20 for mysql and S30 for apache so the database would come up first. It works, and you can see exactly what it'll do. It's also entirely manual, and that's the seed of every problem below.

Note

An /etc/init.d script isn't magic — it's a shell script that takes a verb as its argument: start, stop, restart, status, reload. The symlink farm just calls each one with start or stop at the right moment. You can run them by hand the same way: /etc/init.d/mysql start. That direct, no-abstraction simplicity is exactly what people loved about it.

Driving It

You don't usually edit the symlink farm by hand. Two commands manage it for you.

# Start, stop, restart, check a service (the everyday command)
service mysql start
service mysql status
service apache2 restart

# Manage which runlevels a service starts in (the "enable at boot" command)
chkconfig --list nginx          # which runlevels start it (RHEL family)
chkconfig nginx on              # create the S-links so it starts at boot
chkconfig nginx off             # remove them
update-rc.d nginx defaults      # the Debian/Ubuntu equivalent of chkconfig on

The service command is just a wrapper that finds the right /etc/init.d script and calls it with your verb. chkconfig (RHEL/CentOS) and update-rc.d (Debian/Ubuntu) are the tools that create and remove the S/K symlinks for you, so you don't have to remember which rcN.d directories to touch or what number to give the link. "Enable nginx at boot" really means "make a correctly-numbered S symlink in the right runlevel directories" — these tools just spare you the bookkeeping.

Pro Tip

service survived the transition: on a modern systemd box, service mysql start still works — it's a compatibility shim that quietly calls systemctl. So the muscle memory you build here isn't wasted; it's the one command that reads the same across thirty years of Linux.

Why It Was Simple but Slow

sysvinit's strengths and weaknesses are the same trait seen from two sides: it runs shell scripts, in order, one at a time.

The upside is real and worth respecting. Everything is a script you can read, modify, and run by hand. There's no daemon mediating, no binary log format, no dependency engine to second-guess. When a service won't start, you open its script in /etc/init.d, read what it does, and you know. For a small box, this transparency is a genuine pleasure.

But two flaws sank it as servers grew:

  • Sequential boot. sysvinit starts service S20 and waits for it to finish before starting S30. If S20 is a network mount that hangs for thirty seconds, everything behind it waits thirty seconds. Boot time is the sum of every script's runtime, in a row, even though most of them have nothing to do with each other and could happily start at once. On a server with twenty services this adds up fast, and there's no parallelism to claw it back.
  • Dependency-blindness. sysvinit doesn't understand that apache needs mysql; it only knows S30 sorts after S20. Get the numbers wrong and a service starts before the thing it depends on, fails, and there's no built-in retry — sysvinit started the script, the script exited non-zero, and sysvinit moves on without a backward glance. There's also no supervision: if mysql crashes an hour after boot, sysvinit does nothing. It started the script once; watching the process afterward was never part of the design. (That gap is exactly what upstart and systemd were built to close — and the reason you should run long-lived programs as supervised units today, not as fire-and-forget scripts.)

Put those together — slow because serial, fragile because ordering is manual, and blind because nothing watches a service after it starts — and you have the case for everything that came next.

Where You'll Meet It Today

Plainly: on new servers, you won't, and you shouldn't try to. Every mainstream distribution — Debian, Ubuntu, RHEL, Fedora, SUSE — moved to systemd years ago. Choosing sysvinit for a fresh deployment in the current decade means giving up parallel boot, service supervision, and real dependency handling for no benefit your future self will thank you for.

Where you will meet it is on the long tail of old infrastructure: a CentOS 6 box someone forgot to retire, an embedded appliance, a minimal container image that deliberately ships a tiny init, or a tutorial written in 2012 that tells you to edit /etc/inittab. Knowing sysvinit is a reading skill — it lets you understand the old machine and the old docs without flailing. When you hit one, the playbook is: find PID 1, look in /etc/init.d, read the script, use service and chkconfig. It'll all be there in plain sight, which was always the point.

Cheat Sheet

# --- Identify it ---
ps -p 1 -o comm=                 # prints 'init' on a sysvinit box
cat /etc/inittab                 # the one config file; default runlevel lives here

# --- Run a service ---
service <svc> start|stop|restart|status|reload
/etc/init.d/<svc> start          # the same thing, called directly

# --- Boot-time enablement (creates/removes the rcN.d symlinks) ---
chkconfig --list                 # RHEL/CentOS: every service's runlevels
chkconfig <svc> on | off         # RHEL/CentOS
update-rc.d <svc> defaults       # Debian/Ubuntu: enable at boot
update-rc.d <svc> remove         # Debian/Ubuntu: disable

# --- Runlevels ---
runlevel                         # current and previous runlevel
telinit 1                        # switch to single-user mode
ls /etc/rc3.d/                   # the symlink farm for runlevel 3

History and Philosophy

sysvinit's design wasn't invented for Linux — it was inherited. AT&T's UNIX System V, released in 1983, established the runlevel and /etc/rc script model, and when Linux distributions needed an init system in the early 1990s they adopted the System V approach almost wholesale because it was the proven, documented, familiar thing. That's why a concept from a 1983 commercial Unix was still booting your Linux box in 2010: it was good enough, and good enough has remarkable staying power in systems software.

The philosophy is pure early-Unix: mechanism over magic. Don't build a clever daemon that understands services; just provide a place to put scripts and an order to run them in, and let the scripts do the work. It's the same instinct that gives Unix text-stream pipes and "everything is a file" — small, transparent, composable pieces a person can fully understand. For a single workstation or a server you log into and tend by hand, that instinct is exactly right.

What changed was scale. When you're running hundreds of services across thousands of machines that must boot fast and heal themselves, "a person can read every script" stops being the goal and "the machine handles dependencies and restarts without me" starts being it. sysvinit was a faithful answer to the old question. systemd is the answer to the new one. Neither is wrong; they were asked different things — and sysvinit's long reign is the reason so much of Linux's written wisdom still speaks its dialect.

See Also

  • init system — the concept, PID 1, and the whole family tree
  • runlevel — the numbered modes sysvinit boots into
  • upstart — the event-driven init that tried to replace it
  • systemd — the modern init that actually did
  • process — what PID 1 is the root of
  • daemon — the long-running services those scripts start
  • service — the unit of "something that should be running"
  • service — the everyday command, still works on systemd
  • systemctl — its modern replacement

Stuck reading shell scripts to find out why a service didn't come up?

CleverUptime reads /proc to see which services are actually running right now and flags the one that should be up but isn't — no matter which init started it.

Want to see your own server's health right now? One command, no signup, no install.

Check your server →