Boot into Btrfs snapshots from GRUB on Fedora (grub-btrfs + snapper)
Published 11 July 2026
- date
- updated
- env
- linux Fedora 44 — Btrfs-on-LUKS root, SEPARATE ext4 /boot, UEFI, snapper already configured macos N/A
- tags
Plain snapper lets you recover while the system still boots: you log in, inspect the damage, revert files, or replace the root subvolume. It cannot help when an update breaks booting itself — a bad kernel, a broken initramfs, a botched upgrade that leaves you at a black screen. You can’t run snapper because you can’t get to a working system.
grub-btrfs closes that gap. It injects a “Fedora Linux snapshots” submenu into the GRUB boot screen, one entry per snapper snapshot. You pick a snapshot at boot and the system starts directly into that older state of / (read-only), entirely from the bootloader — so it works even when the normal system won’t. From there you can confirm the fix and make the rollback permanent.
This is the optional follow-on to Snapper restore points on Fedora (Btrfs). Do that setup first.
Optional / third-party.
grub-btrfsis not in Fedora’s repos and touches the bootloader, and it interacts awkwardly with this machine’s separate ext4/boot(see the caveat section). Read the caveats before deciding to install it. Steps 1 to 5 were applied and verified on this machine on the nested/.snapshotslayout this how-to was originally written against — including the.snapshots.mountfix in Step 3, which Step 3 now explains when to skip. Re-verified end to end on 2026-08-14 on a fresh Fedora 44 install using the top-level/.snapshotslayout (case A), where the packaged unit works as shipped and no override is needed.
Read this first — the separate-/boot caveat
On a default Fedora install (this machine included) /boot is its own ext4 partition, not part of the Btrfs root. Confirm with:
findmnt /bootIf /boot shows FSTYPE ext4 (or is the EFI System Partition), then:
- The kernel image and initramfs are NOT captured in snapshots. Snapshots only cover the Btrfs
/subvolume./bootis shared across all snapshots. - Kernel modules (
/usr/lib/modules/…) ARE in the snapshot (they live under/). So a snapshot can contain modules for a kernel version whose image no longer exists in/boot, or vice-versa — a version mismatch when you roll back across a kernel update. - What still works well: rolling back userspace and configuration — a bad
dnf upgradeof packages, a broken config in/etc, a regression in an app. This is the common case and grub-btrfs handles it fine. - What is unreliable: rolling back across a kernel change using a snapshot entry, because the snapshot’s
/bootis empty (it’s just a mountpoint) and the running kernel/initramfs come from the shared ext4 partition. - Good news for kernel problems specifically: Fedora keeps the last 3 kernels in
/boot(installonly_limit=3), and GRUB’s normal “Advanced options for Fedora Linux” submenu already lets you boot an older kernel. For “the new kernel won’t boot,” that built-in menu is usually the better tool than a snapshot.
Bottom line: grub-btrfs on this layout is best understood as “boot an old root filesystem state to recover userspace,” not “a full point-in-time machine restore including the kernel.” If you want kernel-inclusive rollbacks, that requires putting /boot inside the Btrfs subvolume — a reinstall-level change, out of scope here.
Prerequisites
- snapper already installed and configured with a
rootconfig and a populated/.snapshots(see the snapper how-to). Verify:Terminal window sudo snapper -c root listls /.snapshots - Btrfs root filesystem.
/bootmay be separate (see caveat above). - GRUB as the bootloader with its config at
/boot/grub2/grub.cfg(Fedora default on both UEFI and BIOS since Fedora 34 — the EFI stub at/boot/efi/EFI/fedora/grub.cfgjust chainloads it). Confirm:Terminal window ls -l /boot/grub2/grub.cfg sudo/ root privileges.- Willingness to run a third-party COPR that modifies bootloader config.
Step 1 — Install grub-btrfs from the kylegospo COPR
grub-btrfs is not in Fedora’s repositories. The kylegospo fork is the well-maintained one patched to work with Fedora’s BLS (BootLoaderSpec) boot entries — which is why you do not need to disable BLS in /etc/default/grub with this fork.
sudo dnf copr enable -y kylegospo/grub-btrfssudo dnf install -y grub-btrfsIt does not pull in
inotify-tools, and does not need to. Most grub-btrfs guides say the install brings ininotify-toolsfor the watcher — that is true of the upstreamgrub-btrfsddaemon, which shells out toinotifywait. The kylegospo package ships a systemd.pathunit instead, and a path unit uses systemd’s own inotify. Verified on 2026-08-14:rpm -q --requires grub-btrfslists no inotify dependency, the package is not installed, and the watcher works. If a guide tells you to install it, you are reading about the other packaging.
Step 2 — Confirm the Fedora paths in grub-btrfs config
The COPR package ships Fedora-correct defaults, but verify /etc/default/grub-btrfs/config contains (uncommented) the Fedora values:
grep -E 'MKCONFIG|GRUB_DIRNAME|SCRIPT_CHECK' /etc/default/grub-btrfs/configExpected on Fedora:
GRUB_BTRFS_MKCONFIG=/usr/bin/grub2-mkconfigGRUB_BTRFS_GRUB_DIRNAME="/boot/grub2"GRUB_BTRFS_SCRIPT_CHECK=grub2-script-checkGRUB_BTRFS_MKCONFIG_LIB=/usr/share/grub/grub-mkconfig_lib/usr/bin/grub2-mkconfig and /usr/sbin/grub2-mkconfig are the same file — Fedora is usrmerged and /usr/sbin is a symlink to bin, so either value works. The package ships the /usr/bin form.
If any are missing or point at Debian-style paths (/usr/sbin/grub-mkconfig, /boot/grub), edit the file and correct them:
sudoedit /etc/default/grub-btrfs/configOther useful knobs in that file: GRUB_BTRFS_LIMIT (max snapshot entries to list, default 50), GRUB_BTRFS_TITLE_FORMAT (what each entry shows — date, type, description), and GRUB_BTRFS_SHOW_SNAPSHOTS_FOUND.
Step 3 — Enable the watcher (with the Fedora .snapshots.mount fix)
A systemd unit watches /.snapshots and rebuilds the GRUB snapshot submenu whenever snapper creates or deletes a snapshot, so the boot menu always matches your current snapshots. Check what the package shipped:
systemctl list-unit-files 'grub-btrfs*'The current kylegospo COPR ships a path unit plus a static service it triggers:
grub-btrfs.path disabled ← this is the one you enablegrub-btrfs.service static ← triggered by the path unit; do NOT enable directlyThe Fedora gotcha — and whether it applies to you
The packaged unit hard-binds to a .snapshots.mount systemd unit:
[Unit]Requires=\x2esnapshots.mountAfter=\x2esnapshots.mountBindsTo=\x2esnapshots.mount[Install]WantedBy=\x2esnapshots.mountsystemd only generates that unit when /.snapshots is a separately mounted filesystem — an fstab entry, or an explicit mount. Whether it exists therefore depends on how /.snapshots was set up, and that is exactly the choice made in Step 1b of the snapper how-to. Check which case you are in before enabling anything:
findmnt /.snapshotssystemd-escape -p --suffix=mount /.snapshots # the unit name systemd would usesystemctl list-units --all --type=mount | grep -i snapshotsCase A — /.snapshots is a top-level subvolume mounted from fstab (the layout recommended by the snapper how-to). findmnt prints a line, and systemctl shows the mount unit under its escaped name — verified on this machine:
\x2esnapshots.mount loaded active mounted /.snapshotsThat is precisely the name the packaged unit references. Nothing to fix: skip to the enable step below, and skip the override entirely. The binding is a feature here — the watcher starts and stops with the mount it watches.
Case B — /.snapshots is a nested subvolume inside / (what snapper create-config produces if left alone). findmnt prints nothing, no mount unit exists, and enabling the path unit fails:
$ sudo systemctl enable --now grub-btrfs.pathUnit /usr/lib/systemd/system/grub-btrfs.path is added as a dependency to a non-existent unit .snapshots.mount.Failed to start grub-btrfs.path: Unit .snapshots.mount not found.Apply the override below.
The fix for case B — a local override
Replace the unit with one that drops the mount dependency and attaches the watcher to a normal target. A file at /etc/systemd/system/grub-btrfs.path fully overrides the packaged one at /usr/lib/systemd/system/ and survives package updates. Its contents:
# Local override of /usr/lib/systemd/system/grub-btrfs.path# The packaged unit binds to a `.snapshots.mount` unit that only exists when# /.snapshots is separately mounted (openSUSE-style). On Fedora's nested-subvol# layout that unit doesn't exist, so drop the dependency and use a real target.[Unit]Description=Monitors for new snapshots
[Path]PathModified=/.snapshotsUnit=grub-btrfs.service
[Install]WantedBy=multi-user.targetPaste tip: don’t paste this as a
sudo tee <<'EOF'here-doc if your shell mangles indented multi-line input. Write it to a normal file and copy it in with a single line:sudo install -D -m 0644 <file> /etc/systemd/system/grub-btrfs.path(or usesudoedit /etc/systemd/system/grub-btrfs.path).
Enable the watcher (both cases)
sudo systemctl daemon-reloadsudo systemctl enable --now grub-btrfs.pathsystemctl status grub-btrfs.path --no-pagerExpect Active: active (waiting) and Triggers: ● grub-btrfs.service. The Loaded: line tells you which unit is in force — /etc/systemd/system/grub-btrfs.path in case B, /usr/lib/systemd/system/grub-btrfs.path in case A. You do not need to change the watch path — /.snapshots is snapper’s default in both layouts.
Step 4 — Generate the snapshot menu for the first time
Trigger the snapshot menu generator, then rebuild the main GRUB config:
sudo /etc/grub.d/41_snapshots-btrfssudo grub2-mkconfig -o /boot/grub2/grub.cfgWatch the output for a line like Found snapshot … per snapper snapshot and Found N snapshot(s). If it finds zero, snapper hasn’t populated /.snapshots yet — create one (sudo snapper -c root create -d test) and rerun.
Gotcha — run these from a directory root can enter
Both commands fail if your shell’s current directory is one that root cannot access. The symptom is easy to misread, because it mostly looks like nothing happened:
$ sudo /etc/grub.d/41_snapshots-btrfs && sudo grub2-mkconfig -o /boot/grub2/grub.cfg$No output at all from the generator (zero bytes on both stdout and stderr, exit status 1), and from grub2-mkconfig only:
/usr/bin/grub2-probe: error: cannot restore the original directory.grub2-probe saves its working directory and restores it afterwards. Under sudo the working directory is inherited from your shell, so if root cannot chdir() back into it, the tool aborts before doing any work. The classic trigger is a FUSE mount owned by your user and mounted without allow_other — root is not allowed in, by design. On this machine that is the rclone Dropbox mount from Mount Dropbox on Fedora with rclone: running the step from anywhere under ~/Dropbox breaks it.
What makes it nasty is that it fails silently and without side effects: /boot/grub2/grub-btrfs.cfg is never created and grub.cfg keeps its old timestamp, so nothing looks damaged — you simply get no snapshot submenu, with no error to search for.
Fix: run from a directory root can enter. A subshell keeps your own working directory unchanged:
(cd / && sudo /etc/grub.d/41_snapshots-btrfs && sudo grub2-mkconfig -o /boot/grub2/grub.cfg)Confirm it actually wrote something — this is the check worth doing, since success and silent failure look alike:
sudo ls -l /boot/grub2/grub-btrfs.cfgThis applies to every sudo command in this how-to, not just Step 4, and to grub2-mkconfig generally — including the manual rebuild below and the removal step.
Step 5 — Reboot and verify
sudo systemctl rebootAt the GRUB screen you should now see an extra entry:
Fedora LinuxAdvanced options for Fedora LinuxFedora Linux snapshots ← new └─ 2026-07-11 18:43 pre dnf install └─ 2026-07-11 12:00 single timeline └─ ...Open it, pick a snapshot, and the system boots into that state read-only (a safety measure — you’re inspecting, not yet committing). You are now running the old root filesystem.
Recovering: make a rollback permanent
Do not use
snapper rollbackfor this on Fedora. It is the command every guide reaches for, and on a stock Fedora install it is silently ignored: it only repoints the filesystem’s default subvolume, while both/etc/fstaband the kernel command line (rootflags=subvol=root) pin the root subvolume by name. You reboot into the unchanged system, with no error to tell you why. The full explanation and the procedure that works are in the snapper how-to: Rolling back the whole system. The short version is below.
Booting a snapshot from the GRUB menu is temporary — it changes rootflags= for that one boot only. The normal menu entry still points at subvol=root, so the next ordinary boot lands back in the broken system. Making the recovery permanent means putting the snapshot’s content back under the name root.
You have two paths depending on whether the system still boots normally.
A) System still boots normally — you don’t need grub-btrfs at all. Prefer the least destructive option that fixes the problem:
- Find what changed, using the pre/post pair from the transaction that broke things:
Terminal window sudo snapper -c root listsudo snapper -c root status <pre>..<post> - If it is a handful of files, revert just those — no reboot, no rollback:
Terminal window sudo snapper -c root undochange <pre>..<post> - Only if the system is too broken to repair file by file, swap the root subvolume as described in the snapper how-to, then reboot.
B) System won’t boot — this is what grub-btrfs is for:
- At the GRUB menu, open Fedora Linux snapshots and boot a known-good snapshot (read-only).
- Confirm it’s actually the state you want (log in, check the thing that broke is fine). Note which snapshot number you are running:
Terminal window findmnt -no OPTIONS / # → subvol=/.snapshots/<N>/snapshot - Make it permanent by swapping the subvolume. The booted snapshot is read-only, but that does not matter — you are writing to the filesystem, not to the snapshot:
Terminal window sudo mkdir -p /mnt/btrfs-topsudo mount -o subvolid=5 "$(findmnt -no SOURCE / | sed 's/\[.*//')" /mnt/btrfs-topsudo mv /mnt/btrfs-top/root /mnt/btrfs-top/root.brokensudo btrfs subvolume snapshot /mnt/btrfs-top/.snapshots/<N>/snapshot /mnt/btrfs-top/root - Reboot into the restored, writable system — pick the normal Fedora entry this time, not a snapshot entry:
Terminal window sudo systemctl reboot - Once satisfied, reclaim the space (
root.brokencontains nested subvolumes; remove those first). See the snapper how-to for the details and for keepingroot.brokenas a way back.
If your
/.snapshotsis a top-level subvolume (Step 1b of the snapper how-to), your snapshot history survives this swap untouched, including the state you just rolled away from. If it is nested insideroot, the history goes withroot.broken— do not delete that subvolume until you have what you need out of it.
On this machine’s separate
/boot, if the failure was a kernel/initramfs problem rather than a userspace one, prefer GRUB’s “Advanced options for Fedora Linux” to boot a previous kernel (see the caveat section) — a root-filesystem snapshot won’t swap the kernel.
Keeping the menu fresh
With the watcher from Step 3 enabled, the snapshot submenu regenerates automatically as snapper adds/removes snapshots — including the pre/post pairs your dnf hook creates. To rebuild it by hand at any time:
(cd / && sudo /etc/grub.d/41_snapshots-btrfs && sudo grub2-mkconfig -o /boot/grub2/grub.cfg)The cd / is not cosmetic — see the working-directory gotcha in Step 4.
Optional — hide the GRUB menu until something goes wrong
Once the snapshot submenu exists, the boot menu sits on screen for GRUB_TIMEOUT seconds at every boot, while you only ever need it when something is broken. Fedora ships a mechanism that hides it on healthy boots and brings it back by itself after a failed one — which is precisely the moment the snapshot entries matter.
This is not GRUB_TIMEOUT=0, which hides the menu unconditionally, including when you need it. It is a variable in GRUB’s environment block:
sudo grub2-editenv - set menu_auto_hide=1No grub2-mkconfig is required: the logic is already in your generated grub.cfg, contributed by three snippets grub2-tools installs by default.
| Snippet | Role |
|---|---|
/etc/grub.d/10_reset_boot_success | sets menu_hide_ok=1 if the previous boot was flagged successful, then resets boot_success=0 for the current boot |
/etc/grub.d/12_menu_auto_hide | when menu_auto_hide=1 and menu_hide_ok=1 → timeout_style=hidden, timeout=1 |
/etc/grub.d/14_menu_show_once | displays the menu once on request, then clears the request |
Confirm they made it into your config — verified present on Fedora 44 with grub2-tools-2.12-60.fc44:
sudo sh -c 'grep -c menu_auto_hide /boot/grub2/grub.cfg; grub2-editenv - list'A non-zero count plus menu_auto_hide=1 in the listing is all you need. The setting takes effect at the next boot.
What counts as a “failed boot”
boot_success is set to 1 from userspace by grub-boot-success.timer, a user timer that fires 2 minutes after your graphical session starts. It costs nothing at boot time — it writes a flag while you work.
| Previous boot | grubenv at next boot | What you see |
|---|---|---|
| Session ran ≥ 2 minutes | boot_success=1 | no menu — a 1-second hidden window where a keypress (ESC) reveals it |
| Kernel panic, broken initramfs, black screen, session never opened | boot_success=0 | the full menu with your GRUB_TIMEOUT, snapshot submenu included |
| You rebooted within 2 minutes of logging in | boot_success=0 | the full menu — a harmless false positive |
Offline update (system-update.target) | boot_indeterminate incremented by grub-boot-indeterminate.service | hidden once, then treated as failed if that boot doesn’t succeed either |
The two failure modes that send you looking for a snapshot — it doesn’t boot at all, and it boots but the desktop is unusable so you reboot immediately — both land on boot_success=0. You get the menu without doing anything.
Asking for the menu on purpose
The case auto-hide does not cover: the system boots perfectly, and three days later you decide to roll back. boot_success=1, so the menu is hidden and you are left spamming ESC at a 1-second window. From a system that still boots, request it explicitly instead:
sudo grub2-editenv - set menu_show_once_timeout=10The next boot shows the menu for 10 seconds, then the variable clears itself. menu_show_once=1 does the same with a fixed 60-second timeout — the menu is interactive, so pressing Enter boots immediately; the timeout only elapses if you walk away.
Caveats
fastboot=1removes the escape hatch. If that variable is set ingrubenv,12_menu_auto_hideusestimeout_style=menuwithtimeout=0instead, which skips the keypress check entirely — the menu becomes unreachable from the keyboard. Check for it withgrub2-editenv - listbefore relying on ESC.- GRUB must be able to write
grubenv. The whole mechanism rests on GRUB resettingboot_success=0at each boot. On this machine/bootis a separate ext4 partition, sosave_envworks normally — the one place where the separate-/bootlayout from the caveat section is an advantage rather than a limitation. GRUB has no write support for Btrfs; Fedora patches around it with theenv_blockraw-offset variable you can see referenced in those snippets. If your/bootlives on Btrfs, verify the flag actually toggles (below) instead of assuming it does. grubenvis notgrub.cfg. The regenerations grub-btrfs triggers on every new snapshot rewritegrub.cfgandgrub-btrfs.cfg, and leavegrubenvalone. Your setting survives them.
Verify it, and reverting
The flag is observable from the running system. Right after logging in (under 2 minutes) it should read boot_success=0; a few minutes later, boot_success=1:
sudo grub2-editenv - listTo go back to a permanently visible menu:
sudo grub2-editenv - unset menu_auto_hideUndoing this setup
grub-btrfs is non-destructive and easy to remove:
sudo systemctl disable --now grub-btrfs.pathsudo rm -f /etc/systemd/system/grub-btrfs.path # remove the local overridesudo systemctl daemon-reloadsudo dnf remove -y grub-btrfssudo grub2-mkconfig -o /boot/grub2/grub.cfg # regenerate a clean menu without snapshot entriesYour snapshots and snapper config are untouched — you only remove the boot-menu integration.
Notes and good practices
- Third-party component. The kylegospo COPR is community-maintained, not Fedora-official. It can lag or break after a major GRUB /
grub2-toolingupdate; re-rungrub2-mkconfigand check the boot menu after such updates. - BLS stays enabled. With the kylegospo fork you keep Fedora’s default
GRUB_ENABLE_BLSCFG=true— do not disable BLS (that’s only needed for the upstream Antynea version or openSUSE-style layouts). - Snapshots are still not a backup. They live on the same disk. grub-btrfs improves recovery convenience, not durability. Keep an off-disk backup.
- Same recovery without grub-btrfs: boot a Fedora live USB, unlock LUKS, mount the Btrfs top level (
subvolid=5), and swap therootsubvolume there. grub-btrfs saves you the USB and lets you verify the snapshot by running it before committing to it — it does not unlock a recovery you couldn’t otherwise perform. - The watcher’s mount binding is layout-dependent. If you later move
/.snapshotsbetween the nested and top-level layouts, revisit Step 3: the packaged unit works in one case and fails in the other. A watcher that silently stopped starting leaves you with a stale snapshot menu, which is worse than no menu at all — it lists snapshots that may no longer exist. - Keep snapper descriptions ASCII. Snapshot descriptions become GRUB menu titles, and non-ASCII characters are HTML-escaped on the way in: a description written
baseline — fresh snapper installshows up in the boot menu asbaseline — fresh snapper install. Purely cosmetic, but the boot menu is exactly where you don’t want to squint. Use a plain-. - Read-only first is intentional. Booting a snapshot read-only prevents you from accidentally writing to a snapshot you might not keep, and the boot itself is a one-off (
rootflags=for that entry only). Nothing is committed until you swap therootsubvolume.
Quick reference
| Action | Command |
|---|---|
Check if /boot is separate | findmnt /boot |
| Enable COPR + install | sudo dnf copr enable -y kylegospo/grub-btrfs && sudo dnf install -y grub-btrfs |
| Verify Fedora paths in config | grep -E 'MKCONFIG|GRUB_DIRNAME|SCRIPT_CHECK' /etc/default/grub-btrfs/config |
| See which watcher unit shipped | systemctl list-unit-files 'grub-btrfs*' |
Which /.snapshots layout do I have? | findmnt /.snapshots (a line = mounted, override not needed; no output = nested, override needed) |
Install the .path override (nested layout only) | sudo install -D -m 0644 <file> /etc/systemd/system/grub-btrfs.path |
| Enable watcher (path unit) | sudo systemctl daemon-reload && sudo systemctl enable --now grub-btrfs.path |
| Live-test the watcher | sudo snapper -c root create -d test && sleep 3 && systemctl status grub-btrfs.service --no-pager |
| Regenerate snapshot menu | (cd / && sudo /etc/grub.d/41_snapshots-btrfs && sudo grub2-mkconfig -o /boot/grub2/grub.cfg) |
| Nothing happened / no output at all? | You ran it from a directory root can’t enter (e.g. a FUSE mount like ~/Dropbox). Re-run from / — see Step 4 |
| Did the menu actually get written? | sudo ls -l /boot/grub2/grub-btrfs.cfg |
| Which snapshot am I booted into? | findmnt -no OPTIONS / → subvol=/.snapshots/<N>/snapshot |
| Revert a few files (preferred) | sudo snapper -c root undochange <pre>..<post> |
| Make a rollback permanent | swap the root subvolume — not snapper rollback, which is ignored on Fedora |
| Boot an older kernel instead | GRUB → “Advanced options for Fedora Linux” |
| Hide the menu until a boot fails | sudo grub2-editenv - set menu_auto_hide=1 |
| Show the menu once (10 s) at next boot | sudo grub2-editenv - set menu_show_once_timeout=10 |
| Inspect GRUB’s saved state | sudo grub2-editenv - list |
| Make the menu permanently visible again | sudo grub2-editenv - unset menu_auto_hide |
| Remove grub-btrfs | sudo dnf remove -y grub-btrfs && sudo grub2-mkconfig -o /boot/grub2/grub.cfg |
Files / services touched
| Path or unit | Purpose |
|---|---|
/etc/default/grub-btrfs/config | grub-btrfs settings (mkconfig path, grub dir, entry limit/format) |
/etc/grub.d/41_snapshots-btrfs | GRUB generator script that emits the snapshot submenu |
/boot/grub2/grub.cfg | main GRUB config; includes a stub that loads grub-btrfs.cfg |
/boot/grub2/grub-btrfs.cfg | the generated snapshot submenu, refreshed by the watcher |
/etc/systemd/system/grub-btrfs.path | local override dropping the .snapshots.mount dep — only on a nested /.snapshots layout (case B in Step 3) |
grub-btrfs.path → grub-btrfs.service | path watcher that regenerates grub-btrfs.cfg when snapshots change |
/boot/grub2/grubenv | GRUB environment block: saved_entry, boot_success, menu_auto_hide — untouched by menu regeneration |
/etc/grub.d/10_reset_boot_success, 12_menu_auto_hide, 14_menu_show_once | Fedora’s menu auto-hide logic, shipped by grub2-tools (optional section above) |
grub-boot-success.timer (user unit) | flags the boot successful 2 minutes into the graphical session |