DEV Community

Cover image for Stop Fighting GRUB Menus: Practical systemd-boot with bootctl on Linux
Lyra
Lyra

Posted on

Stop Fighting GRUB Menus: Practical systemd-boot with bootctl on Linux

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-toolsbootctl and related tools
# Debian/Ubuntu-style
sudo apt install systemd-boot systemd-boot-efi systemd-boot-tools
Enter fullscreen mode Exit fullscreen mode

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/efi when 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
Enter fullscreen mode Exit fullscreen mode

kernel-install uses the same discovery order for installing kernels into $BOOT.

Inspect before you touch anything

bootctl status
bootctl list
bootctl is-installed
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

What install does (from bootctl(1)):

  1. Installs the systemd-boot EFI binary into the ESP (including the removable-path fallback EFI/BOOT/BOOT*.EFI)
  2. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
  • title comes from PRETTY_NAME when kernel-install generates the file
  • Multiple initrd / options lines 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
Enter fullscreen mode Exit fullscreen mode

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-token if present, else fall through machine-id → IMAGE_ID → ID

Inspect discovery:

kernel-install list
bootctl list
ls "$(bootctl -x)/loader/entries"
Enter fullscreen mode Exit fullscreen mode

Type #2 UKIs (when you want one signed PE file)

Unified Kernel Images live as:

$BOOT/EFI/Linux/<name>.efi
Enter fullscreen mode Exit fullscreen mode

bootctl can classify them:

bootctl kernel-identify /path/to/vmlinuz.efi
bootctl kernel-inspect  /path/to/vmlinuz.efi
Enter fullscreen mode Exit fullscreen mode

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:

  1. UKIs land in EFI/Linux/
  2. bootctl list sees them
  3. Secure Boot signatures match your enrolled keys (again: sbctl territory)

Early boot entropy: random-seed

systemd-boot can pass a solid random seed into the OS very early by combining:

  • /loader/random-seed on the ESP
  • a persistent LoaderSystemToken EFI variable

Initialize both:

sudo bootctl random-seed
Enter fullscreen mode Exit fullscreen mode

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-generator detects counting is active
  • systemd-bless-boot.service runs once the boot is considered successful (boot-complete.target path)
  • 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
Enter fullscreen mode Exit fullscreen mode

You can force:

sudo /usr/lib/systemd/systemd-bless-boot good
sudo /usr/lib/systemd/systemd-bless-boot bad
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

A sane baseline checklist

  1. ESP mounted where bootctl --print-esp-path expects it; XBOOTLDR at /boot if you use one.
  2. bootctl install once; bootctl update after systemd-boot package upgrades.
  3. loader.conf: short timeout, editor no on exposed machines, default glob or @saved.
  4. kernel-install layout matches how you ship kernels (bls vs uki).
  5. bootctl random-seed on first provisioning of bare metal (and unique per machine).
  6. Boot counting + bless-boot enabled if you want automatic bad-kernel fallback.
  7. Verify after every kernel install:
bootctl list
bootctl status
kernel-install inspect
Enter fullscreen mode Exit fullscreen mode
  1. Secure Boot: sign systemd-boot and 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 list and a test reboot look right.
  • Switching away: bootctl remove clears 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


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)