CoursesAdvanced Linux internals & toolingThe boot process & systemd-analyze

The boot process & systemd-analyze

From firmware to a running system, timed.

Advanced12 min · lesson 6 of 17

A cold boot is a relay race. The firmware runs the first leg and hands a baton to the bootloader, the bootloader hands it to the kernel, the kernel hands it to systemd, and systemd sprints the rest of the way to a login prompt. Each runner only knows how to reach the next one. So when a machine won't come up, it didn't fail everywhere at once. It fumbled the baton at exactly one handoff, and knowing the legs of the race tells you which runner to go question.

That same chain is where the quietest persistence lives. Everything here runs before your monitoring wakes up, so an attacker who can plant code early gets it for free on every reboot. Learning to time and read the boot is how you fix slow starts and notice the thing that shouldn't be starting at all.

The Four Handoffs

Here are the legs, in order. Each has one job: get far enough to start the next one.

From power-on to a login prompt
1Firmware (UEFI/BIOS)
POST, find a boot disk, check Secure Boot signature
2Bootloader (GRUB)
load kernel + initramfs, set the kernel command line
3Kernel + initramfs
bring up hardware, mount the real root filesystem
4PID 1 (systemd)
start units in parallel toward the default target
5Default target reached
login prompt (graphical.target or multi-user.target)

The firmware (the low-level code baked into the motherboard, either the older BIOS, Basic Input/Output System, or the modern UEFI, Unified Extensible Firmware Interface) runs first. It wakes the hardware, runs a quick self-check called POST (power-on self-test), and looks for something bootable. On a UEFI machine that means reading a small FAT-formatted partition called the EFI System Partition and running a bootloader program stored there. If Secure Boot is on, the firmware checks that bootloader's cryptographic signature before trusting it. That signature check is your first line of defense against a tampered boot chain.

Next the bootloader, almost always GRUB (GRand Unified Bootloader), takes the baton. GRUB is the menu you sometimes see listing kernels. Its real work is loading two files into memory: the kernel image (vmlinuz) and the initramfs, then handing the kernel a line of text called the kernel command line. That command line, assembled from /etc/default/grub into /boot/grub/grub.cfg, is where boot options live. Remember it. It is the single most useful lever you have when a box won't finish booting.

The kernel (the core of the operating system, the part that talks directly to the hardware) unpacks itself and brings up CPUs, memory, and devices. But it has a chicken-and-egg problem: the drivers it needs to read your real disk might live on that disk. The initramfs (initial RAM filesystem) solves it. It is a tiny throwaway root filesystem loaded straight into memory, carrying enough drivers to find the real root, including the tricky cases: encrypted volumes, LVM (Logical Volume Manager, which pools disks into flexible volumes), software RAID (Redundant Array of Independent Disks), and network storage. Once the initramfs mounts the real root, it switches over to it (switch_root) and starts the first real program.

That first program is PID 1 (process ID 1, the very first userspace process and the ancestor of every other one). On a modern Linux that is systemd (the system and service manager). systemd reads its goal, the default target (a named end-state, systemd's version of the old runlevels), usually graphical.target or multi-user.target. Then it walks the dependency graph and starts everything that target needs, running independent units at the same time instead of one after another. When the target is reached, you get a login prompt.

Putting A Stopwatch On Boot

systemd times every unit it starts, and systemd-analyze reads those numbers back to you. Start with the wide view, which splits total boot time across the stages you just met.

~/secopslog — bash
$ systemd-analyze time
Startup finished in 3.156s (firmware) + 2.201s (loader) + 4.782s (kernel) + 21.345s (userspace) = 31.484s graphical.target reached after 21.234s in userspace

Firmware and loader you mostly can't change. The kernel phase is drivers and hardware init. Userspace, that last and usually largest chunk, is systemd starting your services, and it is the part you can actually shrink. To see which units ate the time, rank them.

~/secopslog — bash
$ systemd-analyze blame | head -8
11.402s cloud-init.service 6.210s NetworkManager-wait-online.service 4.512s snapd.service 4.101s NetworkManager.service 2.998s dev-sda2.device 1.876s man-db.service 1.334s e2scrub_reap.service 987ms systemd-journal-flush.service

Here is the trap. blame is a list of the slowest dishes by cook time, but dinner is served when the last dish on the critical path finishes, and several dishes cooked at the same time. A unit can be slow and still not delay boot at all, because everything waiting on it was already waiting on something else. So shortening the top of blame often changes total boot time by nothing. The honest number comes from the critical chain: the real path of this-waited-for-that that gated the finish.

~/secopslog — bash
$ systemd-analyze critical-chain
The time when unit became active or started is printed after the "@" character. The time the unit took to start is printed after the "+" character. graphical.target @21.234s └─multi-user.target @21.233s └─NetworkManager-wait-online.service @15.001s +6.210s └─NetworkManager.service @10.874s +4.101s └─dbus.service @10.869s └─basic.target @10.702s └─sockets.target @10.688s └─snapd.socket @10.671s +14ms └─sysinit.target @10.550s └─systemd-timesyncd.service @9.980s +0.562s

Read the @ as the clock time when that unit kicked off, and the + as how long it then took to finish coming up. This chain, not the blame list, is what to optimize. The unit holding the finish line here is NetworkManager-wait-online, which started at fifteen seconds and then sat for six more waiting for the network to call itself up. cloud-init, the loudest name in blame, isn't on this path at all, so trimming it wouldn't move the total. Disabling that wait-online unit, or telling it which interface actually matters, is the real six-second win.

Reading What The Boot Actually Did

When boot goes wrong, the logs are your recording of the race. The systemd journal keeps them separately for each boot, so you can walk back through past starts.

~/secopslog — bash
$ journalctl --list-boots
IDX BOOT ID FIRST ENTRY LAST ENTRY -1 3f2504e0e14e11eeba1c0242ac120002 Wed 2026-07-15 08:12:03 UTC Wed 2026-07-16 19:44:51 UTC 0 9b1deb4d3b7d4bad9bdd2b0d7b3dcb6d Thu 2026-07-17 07:30:11 UTC Thu 2026-07-17 09:15:22 UTC

journalctl -b (or -b 0) is this boot; journalctl -b -1 is the one before it. That previous-boot view answers a specific question: did the box reboot cleanly, or did something knock it over? A clean shutdown ends with orderly stop messages. A crash or power loss stops mid-sentence, and the next boot starts fresh. The other quick check is which units never came up.

~/secopslog — bash
$ systemctl --failed
UNIT LOAD ACTIVE SUB DESCRIPTION ● bluetooth.service loaded failed failed Bluetooth service 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. 1 loaded units listed. Pass --all to see loaded but inactive units, too.

systemctl --failed is the morning-after list: every unit that tried to start and gave up. On a healthy machine it should be empty. Anything on it is either your problem to fix or, once in a while, a service you didn't install and didn't expect. Hold that second thought. It comes back later.

When The Baton Drops

Sometimes you can't even reach a login prompt to run those commands. That is what rescue and emergency modes are for, and you reach them by editing the kernel command line from the GRUB menu. At the menu, highlight your normal entry, press e to edit it, find the line that starts with linux, move to its end, add one of the options below, then press Ctrl-X (or F10) to boot that way just this once.

GRUB: edit the linux line
# rescue: single-user mode, root shell, root mounted, almost nothing else running
systemd.unit=rescue.target
# emergency: a root shell even earlier, before most filesystems are mounted
systemd.unit=emergency.target
# or pick an older, known-good kernel from the "Advanced options for ..." submenu

rescue.target gives you a root shell with the root filesystem mounted and services stopped, which is enough to fix a bad config. emergency.target goes further down, a shell before most mounts happen, for when the disk itself is the problem. Once you have a shell, journalctl -b tells you where the last boot stalled and you repair it from there.

One bad line in fstab can hang the whole boot
systemd waits on its dependencies, so a single unit with a long start timeout, or a line in /etc/fstab (the filesystem table, the list of disks the machine mounts at boot) that can't be mounted, can stall boot for minutes or wedge it entirely. A missing USB drive or an unreachable network share is enough. Mark every non-essential mount nofail so its absence never blocks boot, cap device waits with x-systemd.device-timeout, and give custom services a sane TimeoutStartSec. Test fstab edits with findmnt --verify and a reboot you are watching, never blind on a remote box where you have no console access.
/etc/fstab
# <device> <mountpoint> <type> <options> <dump> <pass>
UUID=9c1e... / ext4 errors=remount-ro 0 1
UUID=b7f3... /data ext4 defaults,nofail,x-systemd.device-timeout=10s 0 2

For a service that can block boot, cap how long systemd waits before giving up on it. Running sudo systemctl edit myagent.service opens a drop-in override, a small file layered on top of the shipped unit so you never touch the vendor's copy. Set the timeout there.

/etc/systemd/system/myagent.service.d/override.conf
[Service]
# fail fast instead of hanging boot if the agent can't start
TimeoutStartSec=30s

Boot Is Where Quiet Persistence Lives

Everything in the boot chain runs before your monitoring does. An attacker who can write to a unit file, the initramfs, or GRUB's config gets code that runs early, on every boot, often before the tools that would spot it load. That makes boot prime real estate for persistence. Three habits catch most of it. First, know what starts on its own.

~/secopslog — bash
$ systemctl list-unit-files --state=enabled
UNIT FILE STATE PRESET cron.service enabled enabled ssh.service enabled enabled containerd.service enabled enabled NetworkManager.service enabled enabled systemd-timesyncd.service enabled enabled 5 unit files listed.

Diff that against what you expect to be running. An unfamiliar entry, or a service you never installed showing up in systemd-analyze blame, is both a performance smell and a security one (remember the odd line in systemctl --failed). Second, rate how well your services are boxed in. systemd-analyze security scores each one on how much of the host it could touch if it were compromised.

~/secopslog — bash
$ systemd-analyze security
UNIT EXPOSURE PREDICATE HAPPY ssh.service 9.6 UNSAFE 😨 containerd.service 9.6 UNSAFE 😨 NetworkManager.service 8.4 EXPOSED 🙁 systemd-udevd.service 6.8 MEDIUM 😐 systemd-journald.service 4.5 OK 🙂 systemd-logind.service 2.7 OK 🙂 systemd-resolved.service 2.4 OK 🙂

The score isn't gospel, but it points you at services running with far more privilege than they need, the ones worth hardening with directives like ProtectSystem=strict, PrivateTmp=yes, and NoNewPrivileges=yes. A network-facing daemon sitting at 9.6 UNSAFE is a fine place to start. Third, keep Secure Boot on. That firmware signature check from the very first leg is what stops a tampered bootloader or kernel from loading at all, which closes the earliest link in the chain.

Quick check
01systemd-analyze blame puts cloud-init.service on top at 11s. You trim it to 3s, reboot, and the total barely moves. Which explanation fits what you are seeing?
Incorrect — systemd stamps a start and a finish on every unit it runs, so those figures are measured rather than guessed.
Incorrect — Userspace is normally the fattest of the three slices, which is exactly why it is the one worth attacking.
Correct — Boot finishes when the last unit on the gating path finishes, so a slow unit sitting off that path costs you nothing.
Incorrect — Every boot is timed on its own and the journal files it separately, so there is no stale total waiting to be cleared.
02A teammate wants to delete the initramfs to save a step, on a server whose root filesystem sits on an encrypted LVM volume. Why will that machine stop booting?
Correct — That is the job of the memory-resident root: carry just enough driver code to reach a disk you cannot open yet.
Incorrect — The kernel image is already loaded and running by the time this matters, so the gap it fills is drivers, not size.
Incorrect — Only early-boot drivers and a handful of tools ride along in there; user data never does.
Incorrect — The menu and its config live where the bootloader can already reach them, and cosmetics were never the reason for this step.
03You add a line for a network share to /etc/fstab on a remote box you only reach over SSH, reboot, and it never comes back. What most likely happened?
Incorrect — A plain data mount is handled by systemd straight from the table, with nothing about it baked into the initramfs.
Incorrect — Network mounts sit in fstab quite happily; what decides your fate is the options you hand them.
Incorrect — Identifiers are stable by design, so look for a mount that hung rather than one that stopped matching.
Correct — Mark anything non-essential nofail, cap the wait, and check with findmnt --verify before you reboot a box you cannot see.

Next time a box is slow to boot, don't stare at blame. Pull the critical chain, find the one unit sitting on it, and check whether it is a wait-online you can disable or a mount you can mark nofail. That single line is usually the whole story, and it is the same line an attacker would love you never to read.

Try this

Work through “Boot Is Where Quiet Persistence Lives” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.

Takeaway

The trap worth remembering here: one bad line in fstab can hang the whole boot. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.

Related