The Linux Boot Process
Between pressing the power button and reaching a login prompt, a Linux system passes through several genuinely distinct stages — each handled by a different piece of software, each with its own job, and each capable of failing in its own particular way. This chapter walks through the full sequence, then covers the tools for actually measuring how long each stage took.
Stage 1: Firmware
The very first code that runs is the system’s firmware — either legacy BIOS or modern UEFI. Its job is narrow but essential: run a basic hardware check (the “POST,” or power-on self-test), initialize just enough hardware to proceed, and locate a bootable device. On a UEFI system, this means finding and executing a bootloader from the EFI System Partition directly; on a BIOS system, it means reading a small amount of boot code from a fixed location at the very start of the disk. Either way, firmware’s involvement ends the moment it hands control to the next stage — it doesn’t stick around or stay involved once a bootloader takes over.
Stage 2: The Bootloader
GRUB is the near-universal choice on Debian, Ubuntu, and most other current distributions. Its job is to actually load the Linux kernel into memory and hand control to it. This is also the stage responsible for the boot menu you may have seen briefly at startup — letting you choose between kernel versions, or boot into a recovery mode — configured through /boot/grub/grub.cfg.
GRUB loads two things into memory before handing off: the kernel image itself, and something called the initramfs, covered in its own right in the next stage, since understanding why it exists matters more than just knowing the name.
Stage 3: The Kernel Takes Over
Once GRUB hands off, the kernel begins initializing — decompressing itself, setting up memory management, and loading drivers for the hardware it detects. At this exact point, though, the kernel faces a genuine chicken-and-egg problem: it needs to mount the real root filesystem to continue booting, but mounting that filesystem might itself require a driver or capability the kernel doesn’t have loaded yet — support for LVM, for instance, or a specific storage controller, or an encrypted disk that needs a passphrase before it can even be read.
Stage 4: initramfs — Solving The Chicken-And-Egg Problem
The initramfs (initial RAM filesystem) is a small, temporary filesystem, loaded entirely into memory, containing just enough tools and drivers to get the real root filesystem mounted. The kernel boots into this temporary environment first, uses whatever it needs from it — loading an LVM driver, unlocking an encrypted volume, whatever the specific system requires — and only then mounts the actual root filesystem and switches over to it, a step referred to as switch_root. Once that switch happens, the temporary initramfs environment is discarded entirely, having served its one purpose.
This is exactly why a system using LVM for its main storage still needs /boot (and the kernel and initramfs sitting in it) on a plain, simple, non-LVM partition — the kernel can’t yet understand LVM at the very moment it’s trying to load the initramfs that would teach it how.
Stage 5: systemd Becomes PID 1
Once the real root filesystem is mounted, the kernel starts one final program directly: systemd, which — as covered earlier in this section — becomes PID 1 and takes responsibility for everything from this point forward. It begins working toward the default target, starting each unit in the dependency order those units themselves define, running independent services in parallel wherever their dependencies allow.
Stage 6: Reaching The Default Target
The boot process is considered complete once the default target — multi-user.target on a typical headless server, graphical.target on a desktop — is fully reached, meaning every unit it depends on has successfully started. This is the point where you’d see a login prompt, or where a remote system becomes reachable over SSH.
The Full Sequence, Visually
flowchart TB
A["Firmware (BIOS/UEFI)<br/>POST, locate boot device"] --> B["Bootloader (GRUB)<br/>loads kernel + initramfs"]
B --> C["Kernel initializes<br/>hardware, memory"]
C --> D["initramfs<br/>loads drivers needed to mount real root"]
D --> E["switch_root<br/>to the real filesystem"]
E --> F["systemd starts as PID 1"]
F --> G["Units start in<br/>dependency order"]
G --> H["Default target reached<br/>(multi-user.target / graphical.target)"]
Measuring Boot Time
systemd tracks exactly how long each stage and each unit took, and exposes it directly:
systemd-analyzeStartup finished in 2.1s (kernel) + 1.8s (initrd) + 4.3s (userspace) = 8.2sThis breaks total boot time into the three major phases from the diagram above — kernel, initramfs, and userspace (everything systemd itself did after taking over).
To find out specifically which services took the longest to start:
systemd-analyze blame 2.104s NetworkManager-wait-online.service
1.220s snapd.service
0.412s systemd-udev-settle.serviceThis is genuinely one of the most useful diagnostic commands for a slow-booting system — sorted longest first, showing exactly where boot time is actually going, rather than guessing.
For understanding why something took as long as it did — not just that it was slow, but what it was waiting on — critical-chain shows the dependency path:
systemd-analyze critical-chainThis traces the chain of units that had to complete, one after another, before the default target could be reached — genuinely useful for spotting a specific unit that’s unnecessarily blocking others behind it in the startup order, rather than running safely in parallel.
What’s Next
You now understand every stage a system passes through on the way to becoming fully running. The next chapter goes deep into the tool that captures everything happening throughout this entire sequence and beyond — journalctl, systemd’s own logging system.