How Linux Boots: The Init Process and Run Levels

how linux boots, about init process working... what run levels in linux

Every time you power on a Linux machine, a fast, carefully choreographed sequence of events happens in just a few seconds: hardware initializes, the kernel loads, and finally a fully interactive system appears. Understanding this boot process — especially the role of the init process and (historically) run levels — is essential for troubleshooting boot failures, customizing startup behavior, and simply understanding what’s happening “under the hood” of a Linux system.

The Big Picture: Boot Stages

flowchart TD
    A[Power On / BIOS-UEFI POST] --> B[Bootloader - GRUB]
    B --> C[Kernel Loads into Memory]
    C --> D[Kernel Initializes Hardware and Mounts Root Filesystem]
    D --> E[Kernel Starts init - PID 1]
    E --> F[init/systemd Starts Services in Order]
    F --> G[System Reaches Target Run Level]
    G --> H[Login Prompt / Desktop Ready]

Let’s walk through each stage in detail.

Stage 1: Firmware (BIOS/UEFI) and POST

When you press the power button, the motherboard’s firmware — either legacy BIOS or modern UEFI — runs a Power-On Self-Test (POST) to check that essential hardware (CPU, RAM, storage controllers) is working. It then locates a bootable device according to the configured boot order.

Stage 2: The Bootloader

The firmware hands control to a bootloader, almost always GRUB (GRand Unified Bootloader) on Linux systems. GRUB’s job is to:

  1. Present a boot menu (if configured, e.g., to choose between kernel versions or operating systems)
  2. Load the selected Linux kernel into memory
  3. Load an initial RAM disk (initramfs) — a small temporary filesystem containing drivers needed to mount the real root filesystem
  4. Pass control to the kernel

You can view your current GRUB configuration:

sudo cat /boot/grub/grub.cfg | head -40

Stage 3: The Kernel Takes Over

Once loaded, the Linux kernel:

  1. Initializes the CPU, memory management, and detected hardware drivers
  2. Mounts the initramfs as a temporary root filesystem
  3. Uses drivers within the initramfs to locate and mount the real root filesystem (e.g., your / partition on disk)
  4. Switches from the temporary initramfs root to the real root filesystem (a process called pivot_root or switch_root)
  5. Starts the very first user-space process: PID 1, traditionally called init

Stage 4: PID 1 — The init Process

PID 1 is special: it’s the ancestor of every other process on the system, and it’s responsible for starting (and eventually reaping) all other processes. On modern Linux distributions, PID 1 is systemd. On older systems, it was SysVinit’s /sbin/init.

ps -p 1 -o comm=

On a modern Ubuntu or CentOS system, this will print systemd.

Run Levels: The Legacy Model (SysVinit)

Before systemd became standard, Linux systems used SysVinit, which organized system states into numbered run levels. Each run level represented a different operating mode:

Run LevelMeaning
0Halt (shuts down the system)
1Single-user mode (maintenance/rescue mode, no networking)
2Multi-user mode, no networking (Debian-specific meaning)
3Full multi-user mode, text-only (no GUI)
4Undefined/custom (rarely used)
5Full multi-user mode with GUI (X11 display manager)
6Reboot

On SysVinit systems, the default run level was set in /etc/inittab:

id:5:initdefault:

This meant the system would boot directly into run level 5 (graphical mode).

You could check and change run levels manually:

runlevel        # show current run level
sudo init 3     # switch to run level 3

systemd Targets: The Modern Replacement

Modern systemd-based distributions replaced numbered run levels with named targets, which are more flexible and can represent arbitrary combinations of services, not just a single number.

flowchart LR
    A[systemd Boot] --> B[sysinit.target]
    B --> C[basic.target]
    C --> D{Graphical or Text?}
    D -->|Text mode| E[multi-user.target]
    D -->|GUI mode| F[graphical.target]
    F --> E

Mapping Old Run Levels to New Targets

SysVinit Run Levelsystemd Target
0poweroff.target
1rescue.target
2, 3, 4multi-user.target
5graphical.target
6reboot.target

Checking the Current Target

systemctl get-default

Changing the Default Target Permanently

sudo systemctl set-default multi-user.target

This is the modern equivalent of editing /etc/inittab to change initdefault.

Switching Targets Temporarily (Without Rebooting)

sudo systemctl isolate multi-user.target

This is equivalent to the old init 3 command, immediately switching the running system to text-mode (stopping the graphical display manager, for example).

Booting Into Rescue/Emergency Mode

sudo systemctl rescue

Or, to fix a system that won’t boot normally, you can add systemd.unit=rescue.target as a kernel boot parameter at the GRUB menu.

A Practical Example: Diagnosing a Slow Boot

systemd provides built-in tools to analyze exactly how long each part of the boot process took:

systemd-analyze

Example output:

Startup finished in 3.912s (kernel) + 8.221s (userspace) = 12.133s

To see which specific services took the longest:

systemd-analyze blame

To visualize the boot process as a chart:

systemd-analyze plot > boot-chart.svg

Real-World Use Case: Emergency Recovery

Imagine a server won’t boot to a normal login prompt after a bad configuration change. At the GRUB menu:

  1. Highlight the kernel entry and press e to edit boot parameters
  2. Find the line starting with linux and append: systemd.unit=rescue.target
  3. Press Ctrl+X or F10 to boot with this temporary change
  4. The system boots into a minimal single-user rescue shell where you can fix the broken configuration
  5. Reboot normally afterward

This is functionally identical to the old trick of appending single or 1 at the GRUB kernel line on SysVinit systems.

Comparison: SysVinit vs. systemd Boot Model

AspectSysVinitsystemd
PID 1 program/sbin/init/usr/lib/systemd/systemd
Startup styleSequential, script-basedParallel, dependency-based
System statesNumbered run levels (0–6)Named targets
Config file for default state/etc/inittabsystemctl set-default
Boot diagnosticsManual log inspectionsystemd-analyze built-in tooling
SpeedSlower (sequential)Faster (parallel where possible)

Best Practices

Troubleshooting

Problem: System hangs at boot with no error message

Try adding systemd.log_level=debug to the kernel boot parameters at GRUB to get more verbose boot logging.

Problem: System boots into emergency mode unexpectedly

Check for a filesystem mount failure, often from a bad /etc/fstab entry:

journalctl -xb

Problem: GUI doesn’t start, drops to text login

Check the default target and the display manager service:

systemctl get-default
systemctl status gdm    # or lightdm, sddm, etc.

Problem: Need to boot without a broken service starting

Temporarily mask the problematic service from the GRUB rescue shell or a live environment:

sudo systemctl mask problem-service

Problem: systemd-analyze shows one service taking excessively long

Investigate that service’s own logs and dependencies:

journalctl -u slow-service.service --no-pager

Conclusion

The Linux boot process is a layered handoff: firmware to bootloader, bootloader to kernel, kernel to PID 1, and PID 1 to the rest of userspace. While the legacy SysVinit run-level model (0–6) is still worth understanding for older systems and general Linux literacy, systemd‘s target-based model has become the modern standard, offering faster, parallelized startup and much better built-in diagnostics through tools like systemd-analyze. Mastering both models means you can confidently boot, diagnose, and recover any Linux system you encounter.

Further Reading

Exit mobile version