Stop Fighting GRUB Menus: Practical systemd-boot with bootctl on Linux
If your boot story still starts with a hand-edited GRUB config, a mystery ESP path, and “I hope the new kernel shows up,” it is time to switch mental models.
systemd-boot (sd-boot) is a small UEFI boot manager. It does not try to be a full OS. It reads standard Boot Loader Specification entries from the ESP (and optional XBOOTLDR partition), shows a menu, and hands control to a kernel or UKI. Day-to-day management is done with bootctl from the running OS—not by grepping shell scripts under /etc/grub.d.
This guide is operational: install, inspect, set defaults, wire kernel packages through kernel-install, tune loader.conf, enable early entropy, and use boot counting so a bad kernel does not trap you.
Scope note: This is about the boot manager and entry lifecycle. It is not a Secure Boot key enrollment guide (use
sbctl/ firmware tools for that), not A/B image update policy (systemd-sysupdate), and not userspace-only restart (systemctl soft-reboot).
What you need
- UEFI firmware (systemd-boot is UEFI-only)
- An ESP (GPT type
C12A7328-F81F-11D2-BA4B-00A0C93EC93B), typically VFAT - Optional but useful: an Extended Boot Loader partition (XBOOTLDR, GPT type
BC13C2FF-59E6-4262-A352-B275FD6F7172) on the same disk - Packages (Debian/Ubuntu naming):
-
systemd-boot— integration/services -
systemd-boot-efi— EFI binaries -
systemd-boot-tools—bootctland related tools
-
# Debian/Ubuntu-style
sudo apt install systemd-boot systemd-boot-efi systemd-boot-tools
On Fedora/RHEL-family systems the bits usually ship closer to the main systemd/systemd-boot packages; the commands below are the same.
Mental model: ESP, $BOOT, Type #1 vs Type #2
The UAPI Boot Loader Specification defines two entry styles:
| Type | Where | What it is |
|---|---|---|
| #1 | $BOOT/loader/entries/*.conf |
Text snippets pointing at linux + initrd (or efi / uki) |
| #2 |
$BOOT/EFI/Linux/*.efi (and ESP) |
Unified Kernel Images: one PE binary with stub + kernel + initrd + metadata |
$BOOT is:
- XBOOTLDR if it exists
- otherwise the ESP
Recommended mounts (from the BLS):
- Mount $BOOT at
/boot - If ESP and XBOOTLDR are both present, mount the ESP at
/efi - Avoid nesting ESP under
/boot/efiwhen you can—it complicates automount setups
bootctl discovers these paths:
bootctl status
bootctl --print-esp-path
bootctl --print-boot-path # XBOOTLDR if present, else ESP
kernel-install uses the same discovery order for installing kernels into $BOOT.
Inspect before you touch anything
bootctl status
bootctl list
bootctl is-installed
Useful things status reports:
- Firmware / Secure Boot state (as the loader sees it)
- Which loader booted you
- ESP / boot partition paths
- Current default / oneshot / selected entry IDs
- Feature flags the loader advertised via EFI variables
list shows Boot Loader Spec entries plus other discovered options (Windows, firmware setup, EFI shell when present).
Install or update systemd-boot on the ESP
# First install: copy sd-boot into the ESP and register it with firmware
sudo bootctl install
# Later: refresh installed copies when package binaries are newer
sudo bootctl update
# Confirm
bootctl is-installed
What install does (from bootctl(1)):
- Installs the systemd-boot EFI binary into the ESP (including the removable-path fallback
EFI/BOOT/BOOT*.EFI) - Adds the loader to the firmware boot order (unless you pass
--no-variables)
Signed-file note: if a *.efi.signed sibling exists, install/update prefer it—useful when your distro ships pre-signed bootloader binaries for Secure Boot.
Graceful hosts (images, chroots, odd firmware):
sudo bootctl update --graceful
That ignores some failure modes (missing ESP write, foreign loader already present) instead of hard-failing.
To remove systemd-boot from the ESP and firmware list:
sudo bootctl remove
Configure the menu: loader.conf
Create or edit $ESP/loader/loader.conf (often /efi/loader/loader.conf or /boot/loader/loader.conf depending on layout):
# /efi/loader/loader.conf
timeout 5
default @saved
editor no
console-mode keep
random-seed-mode with-system-token
auto-firmware yes
Key knobs from loader.conf(5):
| Setting | Practical meaning |
|---|---|
timeout |
Seconds before default boots. 0 / menu-hidden = no menu unless you hold a key. menu-force = always show, no timeout |
default |
Glob for default entry, or @saved to remember last choice in an EFI variable |
editor |
Allow editing kernel cmdline at the menu. Disable on untrusted physical access |
console-mode |
keep (default), auto, max, or a numeric mode |
random-seed-mode |
off, with-system-token (default), or always
|
auto-entries |
Show/hide auto-discovered foreign loaders |
auto-firmware |
Show “Reboot into firmware” entry |
You can also change timeout/default at runtime without editing files:
# Persistent default (EFI variable)
sudo bootctl set-default '<entry-id-or-glob>'
# Next boot only
sudo bootctl set-oneshot '<entry-id-or-glob>'
# Menu timeout
sudo bootctl set-timeout 5
sudo bootctl set-timeout-oneshot 30
Special IDs resolved by bootctl: @default, @oneshot, @current, @saved.
From a running system you can also ask systemd to reboot into a specific entry or force the menu once:
systemctl reboot --boot-loader-entry='<entry-id>'
systemctl reboot --boot-loader-menu=5
systemctl reboot --firmware-setup
Those depend on the Boot Loader Interface that systemd-boot implements.
Type #1 entries you can read and reason about
A minimal BLS Type #1 snippet looks like this (paths relative to the filesystem that holds the snippet):
# $BOOT/loader/entries/6a9857a393724b7a981ebb5b8495b9ea-6.12.0-amd64.conf
title Debian GNU/Linux
sort-key debian
machine-id 6a9857a393724b7a981ebb5b8495b9ea
version 6.12.0-amd64
architecture x64
options root=UUID=6d3376e4-fc93-4509-95ec-a21d68011da2 ro quiet
linux /6a9857a393724b7a981ebb5b8495b9ea/6.12.0-amd64/linux
initrd /6a9857a393724b7a981ebb5b8495b9ea/6.12.0-amd64/initrd.img-6.12.0-amd64
Rules that matter in production:
- Filenames are identifiers, not UI titles (restricted charset: alnum,
+,-,_,.) - Prefer including entry-token/machine-id + kernel version in the filename to avoid multi-boot clashes
-
titlecomes fromPRETTY_NAMEwhenkernel-installgenerates the file - Multiple
initrd/optionslines are allowed and are combined in order - On EFI, Linux images should be EFI PE stubs (
CONFIG_EFI_STUB)
You rarely hand-write these on a package-managed host. You let kernel-install do it.
Let kernel-install own kernel placement
# See what the host will do for the running kernel
kernel-install inspect
# Install current or explicit kernel
sudo kernel-install add "$(uname -r)" /boot/vmlinuz-$(uname -r) /boot/initrd.img-$(uname -r)
# Or every kernel under /usr/lib/modules (where layout supports it)
sudo kernel-install add-all
# Remove an old version cleanly
sudo kernel-install remove 6.11.0-amd64
Layout selection (install.conf / env):
-
layout=bls— Type #1 under$BOOT/loader/entries/+ files under$BOOT/<entry-token>/<version>/ -
layout=uki— Type #2 copy into$BOOT/EFI/Linux/<entry-token>-<version>.efi -
layout=auto— UKI if the image is a UKI; else BLS if entries already look BLS-like
Entry token selection (bootctl install --entry-token= / kernel-install --entry-token= / /etc/kernel/entry-token):
-
machine-id— best for multiple parallel installs of the same OS on one disk -
os-id/os-image-id— stable names, but collide if two installs share the same ID -
auto— read/etc/kernel/entry-tokenif present, else fall through machine-id → IMAGE_ID → ID
Inspect discovery:
kernel-install list
bootctl list
ls "$(bootctl -x)/loader/entries"
Type #2 UKIs (when you want one signed PE file)
Unified Kernel Images live as:
$BOOT/EFI/Linux/<name>.efi
bootctl can classify them:
bootctl kernel-identify /path/to/vmlinuz.efi
bootctl kernel-inspect /path/to/vmlinuz.efi
Possible identify results: uki, addon, pe, unknown.
Building UKIs is usually ukify + (optionally) systemd-measure for PCR policies—that is a full article on its own. For day-2 ops with systemd-boot you mainly need:
- UKIs land in
EFI/Linux/ -
bootctl listsees them - Secure Boot signatures match your enrolled keys (again:
sbctlterritory)
Early boot entropy: random-seed
systemd-boot can pass a solid random seed into the OS very early by combining:
-
/loader/random-seedon the ESP - a persistent
LoaderSystemTokenEFI variable
Initialize both:
sudo bootctl random-seed
Default random-seed-mode with-system-token only credits the ESP seed when the system token exists—this avoids identical seeds when the same disk image is cloned to many machines without unique tokens.
If the random-seed file is marked immutable (read-only attribute), systemd-boot neither updates nor uses it—by design, because a never-updated seed would be identical every boot.
Boot counting: automatic fallback after a bad kernel
systemd-boot supports boot counting: entry filenames encode tries-left / tries-done. On failure to reach a “good” boot, the loader prefers older entries.
Userspace side:
-
systemd-bless-boot-generatordetects counting is active -
systemd-bless-boot.serviceruns once the boot is considered successful (boot-complete.targetpath) - It renames the entry to drop the counters (“bless” = permanently good)
Manual checks:
/usr/lib/systemd/systemd-bless-boot status
# good | bad | indeterminate | clean | dirty
You can force:
sudo /usr/lib/systemd/systemd-bless-boot good
sudo /usr/lib/systemd/systemd-bless-boot bad
Use bad when you know the current entry is toxic and you want it skipped next time without burning remaining tries.
This is the practical answer to “new kernel panic-loops and I have no serial console on site.”
Housekeeping: unlink and cleanup
# Remove one entry and unreferenced payloads
sudo bootctl unlink '<entry-id-or-glob>'
# Dry run first
sudo bootctl --dry-run unlink '*-6.11.*'
# Remove orphaned files for this entry token that no entry references
sudo bootctl cleanup
sudo bootctl --dry-run cleanup
Prefer kernel-install remove for package-shaped kernels so plugins (depmod, loaderentry, uki-copy) stay consistent. Use bootctl unlink/cleanup for orphans and one-off images.
Multi-boot and foreign OS entries
Out of the box, systemd-boot can surface:
- Microsoft Windows Boot Manager
- macOS boot manager
- EFI shell
- Reboot into firmware setup
Toggle discovery with auto-entries / auto-firmware in loader.conf. For “reboot into Windows tonight”:
bootctl list # find the Windows entry id
sudo bootctl set-oneshot auto-windows
# or the concrete id shown by list
systemctl reboot
A sane baseline checklist
-
ESP mounted where
bootctl --print-esp-pathexpects it; XBOOTLDR at/bootif you use one. -
bootctl installonce;bootctl updateafter systemd-boot package upgrades. -
loader.conf: short timeout,editor noon exposed machines,defaultglob or@saved. -
kernel-installlayout matches how you ship kernels (blsvsuki). -
bootctl random-seedon first provisioning of bare metal (and unique per machine). - Boot counting + bless-boot enabled if you want automatic bad-kernel fallback.
- Verify after every kernel install:
bootctl list
bootctl status
kernel-install inspect
-
Secure Boot: sign
systemd-bootand kernels/UKIs with your enrolled keys—do not confuse “loader installed” with “firmware will execute it.”
Rollback / coexistence notes
- Switching to systemd-boot does not require wiping GRUB in the same second. Firmware boot order decides who runs. Keep a known-good GRUB entry until
bootctl listand a test reboot look right. - Switching away:
bootctl removeclears systemd-boot copies and firmware entries it owns; restore your previous loader deliberately. - Soft-reboot and sysupdate operate after or around the bootloader; they do not replace it.
- Portable services / sysext extend the running OS; they are not boot entries.
References
- systemd-boot(7)
- bootctl(1)
- loader.conf(5)
- kernel-install(8)
- systemd-bless-boot.service(8)
- UAPI.1 Boot Loader Specification
- Related: systemd-stub(7), ukify(1), systemd-measure(1)
GRUB still has a place on legacy BIOS and some exotic setups. On ordinary UEFI servers and workstations, systemd-boot + Boot Loader Spec entries + bootctl is the smaller, auditable path: plain-text or UKI entries on a known partition, defaults in EFI variables, and a userspace tool that tells you the truth before you reboot.
Top comments (0)