A lot of Fedora users assume running bootctl install is the whole migration, and that the next reboot hands them systemd-boot. That belief is half right, and the half that fails is the expensive one. Switching loaders on Fedora is really two changes:
kernel-installhas to write Boot Loader Specification entries sd-boot can read- the firmware has to launch the binary
bootctlputs on the ESP
Every step of the first half was verified end to end, and it behaves exactly as documented. On the QEMU virtual machine the lab ran, the firmware refused the binary in every configuration tried, and that is the half you cannot script away.
Worse, the failure is quiet. The tooling finishes cleanly, the NVRAM entry shows up, and the machine boots GRUB anyway. In one test it never came back at all. Everything below ran on a Fedora 45 cloud image with systemd 262.1, GRUB 2.12 and shim 16.1 in November 2026, and the rollback at the end gets you back to the original boot chain byte for byte.
What the system boots today
A stock install boots through a chain you should map before touching anything, and bootctl status prints all of it on one screen:
- the firmware runs shim
- shim loads GRUB from the ESP
- GRUB reads the Boot Loader Specification entries from
/bootthrough its blscfg module - the kernel starts

Three facts from the baseline decide how hard your migration will be.
/bootsits on a btrfs subvolume, not a FAT partition- Secure Boot is on, and shim is the binary the firmware trusts
kernel-installreports layoutother, which sd-boot can’t read
None of that blocks you. If you have followed the steps to update GRUB on Fedora before, this is the pipeline you would be leaving. You move /boot onto an XBOOTLDR partition, flip one config key so kernel-install writes specification entries, and regenerate them. Your ESP can stay at 100 MiB, because with a separate XBOOTLDR partition it never has to hold kernels.
If your machine came from a stock install like the one in guide to installing Fedora 45 Server or Workstation, that is exactly the layout you have, and every command below maps onto it directly.
Give sd-boot a readable /boot
systemd-boot reads only FAT filesystems, and the Boot Loader Specification only promises loaders a filesystem the firmware can read, which in practice means FAT. That is why Fedora’s default layout doesn’t work with it. A first attempt formatted the XBOOTLDR partition as ext4, and entries on it never produced a working boot path.
The fix is a partition carrying the XBOOTLDR type GUID, BC13C2FF-59e6-4262-a352-b275fd6f7172, formatted as FAT32 and mounted at /boot. On the lab machine that partition sat on a second disk; on a single-disk box, it is the same job against free space on your root disk.
sudo sgdisk --typecode=1:BC13C2FF-59e6-4262-a352-b275fd6f7172 /dev/sdb
sudo partprobe /dev/sdb
sudo wipefs -a /dev/sdb1
sudo mkfs.vfat -F 32 -n BLS-BOOT /dev/sdb1
Give the new partition a permanent mount point in /etc/fstab:
UUID=0F0F-17F5 /boot vfat defaults,umask=0077 0 2
Getting the typecode right matters more than it looks. Without that GUID, nothing on the system treats the partition as a boot candidate, whatever filesystem it carries. Mount /boot once the fstab line is in place (sudo mount /boot, or reboot), and bootctl -x reports /boot, confirming it as the directory systemd-boot will scan for entries.

Two cautions before you run this on your own disk. wipefs -a destroys whatever lives on the target partition, so be sure the partition number is the right one, and on a single-disk machine you create the partition first with sgdisk --new from free space. Also note the specification wants the XBOOTLDR partition on the same disk as the ESP; the lab used a second disk with the explicit fstab line above, which works but bypasses automatic discovery, so prefer same-disk on a real install.
The umask=0077 keeps the partition root-only, matching how Fedora ships its own boot partitions. Drop it and any local account can read and overwrite your boot files, so keep it. With /boot readable by the loader, the remaining work is making the entries on it legible.
kernel-install writes the entries
Two config files drive the whole entry pipeline. First is /etc/kernel/install.conf, and on Fedora the value you are changing away from is the default other layout, where grubby writes GRUB-shaped entries that specification-aware loaders can’t use.
# /etc/kernel/install.conf
# layout=bls keeps the Boot Loader Specification entries that both
# GRUB (blscfg) and systemd-boot read; "other" entries are invisible to sd-boot
layout=bls
initrd_generator=dracut
Second comes /etc/kernel/cmdline, which becomes the options line on every entry kernel-install writes. Copy the live values out of /proc/cmdline, and your new entries boot with the same root device and flags the machine uses today. One token does not belong in the copy: the leading BOOT_IMAGE= is GRUB device syntax that the live line carries, so strip it. Watch rootflags too, because while GRUB is still the loader it appends its own copy on btrfs, and an entry generated from the live line at that point carries the flag twice.
root=UUID=d04d5cb8-9cf4-4b6f-8f86-aa556776eeb5 ro rootflags=subvol=root no_timer_check console=tty1 console=ttyS0,115200n8 systemd.firstboot=off
Then regenerate every entry for the installed kernels. The command looks like it fails, and it doesn’t.
sudo kernel-install add-all
On the lab machine that printed:
ERROR: not a btrfs filesystem: /boot
ERROR: not a valid btrfs filesystem: /var
ERROR: not a btrfs filesystem: /boot
Those ERROR lines are noise from layout plugins that expect the ESP layout, and the entry file lands on /boot anyway. Don’t take this on faith on your machine, though: list /boot/loader/entries/ after the run and confirm a .conf file exists for your kernel before you reboot into anything. What matters is what those entries contain.

Each entry is a plain text file under /boot/loader/entries/, one per kernel version, following the Boot Loader Specification.
# Boot Loader Specification type#1 entry
# File created by /usr/lib/kernel/install.d/90-loaderentry.install (systemd 262.1-1.fc45)
title Fedora Linux 45 (Cloud Edition)
version 7.2.15-300.fc45.x86_64
machine-id 0fcbc19fb6ee416da62fc34e72016a81
sort-key fedora
options root=UUID=d04d5cb8-9cf4-4b6f-8f86-aa556776eeb5 ro rootflags=subvol=root no_timer_check console=tty1 console=ttyS0,115200n8 systemd.firstboot=off rootflags=subvol=root systemd.machine_id=0fcbc19fb6ee416da62fc34e72016a81
linux /0fcbc19fb6ee416da62fc34e72016a81/7.2.15-300.fc45.x86_64/linux
initrd /0fcbc19fb6ee416da62fc34e72016a81/7.2.15-300.fc45.x86_64/initrd
Watch the linux path, because it decides whether an entry works. It is relative to the root of the boot partition, with no /boot prefix, and systemd-boot resolves it against the XBOOTLDR partition it reads from. That format isn’t sd-boot specific either. GRUB’s blscfg module reads the same files, so your GRUB menu keeps working during the transition, and they are also what you fall back on when you need to boot an older Fedora kernel from the boot menu.

Once an entry resolves cleanly, the tooling half of your migration is done. What is left is staging the loader binary and finding out whether your firmware will run it.
bootctl install does the rest
One prerequisite comes first. The EFI binaries live in the systemd-boot-x64 package, which Fedora does not install by default, so add it before anything else. It ships the signed loader under /usr/lib/systemd/boot/efi/, which the next step copies onto the ESP.
sudo dnf install systemd-boot-x64
Then stage the loader, and the bootctl manual documents each step it performs. Its output is worth reading line by line, because every line is something you would otherwise do by hand.
sudo bootctl install
The lab machine printed the sequence below, and three lines in it deserve a closer look:
Created directory "/boot/efi/EFI".
Created directory "/boot/efi/EFI/systemd".
Created directory "/boot/efi/EFI/BOOT".
Created directory "/boot/efi/loader".
Created directory "/boot/efi/loader/keys".
Created directory "/boot/loader".
Created directory "/boot/loader/entries".
Created directory "/boot/EFI".
Created directory "/boot/EFI/Linux".
Copied "/usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed" to "/boot/efi/EFI/systemd/systemd-bootx64.efi".
Copied "/usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed" to "/boot/efi/EFI/BOOT/BOOTX64.EFI".
Random seed file /boot/efi/loader/random-seed successfully written (32 bytes).
Successfully initialized system token in EFI variable with 32 bytes.
Created EFI boot entry "Linux Boot Manager".
Created EFI boot entry "Fallback Linux Boot Manager".
- the signed loader, copied twice, once as the removable fallback
- a random seed and a system token in an EFI variable
- two NVRAM entries, Linux Boot Manager and its fallback
Both loader copies matter. One sits under EFI/systemd, where the Linux Boot Manager NVRAM entry points at it; the other sits at EFI/BOOT, the removable-media path a UEFI firmware falls back to when its own entries fail. On a Secure Boot system, notice what got copied: the .signed binary Fedora builds for shim verification, because the unsigned one has no chain of trust.
The installed copy hashed identical to the package file, so what lands on your ESP is exactly what the Fedora build signed. Nothing gets re-signed or modified on the way in.

At this point bootctl list shows clean entries and efibootmgr shows Linux Boot Manager at the front of the boot order. On paper, your migration is finished. Nothing has actually booted through systemd-boot yet, and that is where the story splits.
The /boot path trap
Every entry written by the GRUB-era tooling fails to resolve, and the failure mode is a doubled path that is easy to miss if you only skim the output.
linux /boot//boot/vmlinuz… (No such file or directory)
On a grubby-era entry, bootctl list prints:
linux: /boot//boot/vmlinuz-7.2.15-300.fc45.x86_64 (No such file or directory)
initrd: /boot//boot/initramfs-7.2.15-300.fc45.x86_64.img (No such file or directory)
Entries written under the default other layout carry a /boot prefix in their linux and initrd lines, like this grubby-written file.
linux /boot/vmlinuz-7.2.15-300.fc45.x86_64
Specification paths resolve against the partition root. systemd-boot joins that prefix onto the root and looks for /boot/boot/vmlinuz... under the mount point, which does not exist. Why does GRUB boot those same entries without trouble? It tolerates the prefix because it reads the files from inside the mounted tree, which is exactly why they work today and fail under the new loader.
Unknown line ‘grub_users’, ignoring.
Those same entries also carry GRUB-specific keys such as grub_users that systemd-boot doesn’t know. It prints one warning per key and keeps reading, so those are cosmetic. The path problem above is what breaks a boot entry, and both disappear once the entries come from kernel-install under layout=bls.

If bootctl list shows you a doubled path like that, don’t hand-edit the entry files. Switch install.conf to layout=bls, run sudo kernel-install add-all again, and the regenerated entries use partition-relative paths that resolve.
The firmware refused the binary
With everything staged, the lab ran three boot tests, in this order:
- sd-boot first in the boot order, Secure Boot on
- sd-boot first in the boot order, Secure Boot off
- the same NVRAM slot repointed at grubx64.efi, Secure Boot still off
On the first test, the machine came up in GRUB. efibootmgr reported BootCurrent 0002, the Fedora shim entry, so the firmware stepped over the sd-boot entry in front of it and launched shim instead. GRUB kept its product line in bootctl status, and systemd-boot never became the running loader.
So why did the machine still boot GRUB with the new loader first in line? Secure Boot went off for the second test, and the machine reset again. BootCurrent came back as the firmware’s own disk entry, and GRUB came up once more. With verification disabled, the firmware didn’t run the sd-boot binary, and it didn’t run shim from its normal slot either. It fell through to the fallback path on the same disk.
Then came the control test that rules out a partition or file problem. I repointed that same NVRAM slot at grubx64.efi directly, Secure Boot still off, and reset. This time the firmware ran it: BootCurrent showed the repointed slot and GRUB started. Same partition, same FAT filesystem, same firmware. The launcher loads other EFI binaries from that partition without complaint, and the refusal follows the sd-boot binary specifically.

The failure never reached a screen. This virtual machine family exposes no boot console through the capture path, so whether the binary exits quietly, errors out, or the firmware rejects it before execution is unknown. What is known is the outcome: the signed, unmodified, hash-verified loader binary didn’t boot this system on this firmware in any configuration tested, including a copy chained through shim.
Firmware vendors and virtual platform builders lag the specification, and sd-boot on Fedora is exactly the kind of binary a lagging firmware can choke on. So treat your first boot attempt as an experiment, not a migration. Don’t remove or overwrite any GRUB file until a reset has actually landed you in systemd-boot’s menu, and keep the rollback below within arm’s reach while you test.
Secure Boot is a separate question
Fedora’s signed binary changes what the Secure Boot story is. Older guides tell you to disable Secure Boot before touching systemd-boot, and Fedora now ships systemd-bootx64.efi.signed in the systemd-boot-x64 package precisely so you don’t have to. The install step copies that signed file, so the tooling side of Secure Boot support is genuinely there.
Whether your firmware trusts it through shim is a different question, and the attempt to answer it is the one experiment that ended badly. The signed sd-boot binary went over grubx64.efi, the file shim is signed to chain-load, shim went first in the boot order, and the machine reset. It never reached the network again. Recovery came by attaching the disk to a second virtual machine, and that is the recovery path you want identified before you run the experiment, not after.
If you go the shim route, the enrollment workflow is the same one used for custom kernel modules, covered in the guide to Secure Boot and MOK enrollment on Fedora. And if /boot sits inside a LUKS container, stay on GRUB for now: systemd-boot reads only FAT and cannot unlock a LUKS volume, which GRUB handles through its cryptodisk module. Check where the TPM2 unlock flow in the LUKS guide puts its keys before you change anything on such a machine.
Roll back to GRUB cleanly
Before any of the experiments above, copy grubx64.efi aside on the ESP. Every recovery below assumes that backup exists, and it is the difference between a two-command rollback and a rescue boot.
sudo cp /boot/efi/EFI/fedora/grubx64.efi /boot/efi/EFI/fedora/grubx64.efi.grub-backup
sudo cp /boot/efi/EFI/fedora/grubx64.efi.grub-backup /boot/efi/EFI/fedora/grubx64.efi
sudo bootctl remove
On the lab machine, removal printed:
Removed "/boot/efi/loader/loader.conf".
Removed "/boot/efi/loader/random-seed".
Removed directory "/boot/efi/loader/keys".
Removed directory "/boot/efi/loader".
Failed to remove '/boot/EFI': Device or resource busy
Removed EFI variable LoaderConfigTimeout-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f.
Removed EFI variable LoaderSystemToken-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f.
One line in that output looks like a failure and isn’t. Failed to remove '/boot/EFI': Device or resource busy refers to the EFI directory bootctl install created on the boot partition for unified kernel images, and the mounted filesystem keeps it busy. It holds nothing GRUB needs, so leaving it costs nothing. Leave the specification entries in place too, because GRUB’s blscfg module reads the same files and its menu needs them.
After a reset, confirm you are back where you started:
bootctl statusreports GRUB 2.12, Secure Boot enabledefibootmgrshows the Fedora shim entry first in the boot order/boot/efi/EFIholds onlyBOOTandfedora

Hashes before and after the restore matched, so the ESP ended the experiment exactly as it started. That is the property to protect through your own testing. As long as grubx64.efi survives intact and the specification entries stay on /boot, undoing the whole attempt is the two copies and one bootctl remove you see above.
What each piece owns
More moving parts are involved than the one command suggests, and the table below maps who does what.
| Piece | What it does | Where it lives |
|---|---|---|
| shim | First-stage loader the firmware trusts; chains to GRUB | /boot/efi/EFI/fedora/shimx64.efi |
| GRUB | The loader Fedora boots today; reads the specification entries | /boot/efi/EFI/fedora/grubx64.efi |
| bootctl | Installs, inspects and removes systemd-boot | /usr/bin/bootctl |
| kernel-install | Writes the specification entries under layout=bls | /usr/lib/kernel/install.d/ |
| grubby | Writes entries in the default other layout | grubby package |
| systemd-boot | The loader you would switch to; reads only FAT | /boot/efi/EFI/systemd/systemd-bootx64.efi |
| Firmware | Decides which binary runs, and can refuse one | efibootmgr entries |
Everything on the tooling side is one config key, one partition and two commands, and it behaves exactly as documented. The firmware half is the part you can’t script, only test, and the lab refused to cooperate on it. Take the backup, run the boot test on your own machine, and keep the rollback close until a reset has actually shown you systemd-boot’s menu.