A bad dnf update can leave your Fedora system in a state where packages conflict, services refuse to start, or the kernel panics on boot. If your root filesystem is Btrfs, you can undo the entire transaction in seconds. Snapper creates copy-on-write snapshots before and after every package operation, and grub-btrfs lets you boot directly into any of those snapshots if the system won’t come up at all.
This guide covers the full setup: configuring Snapper for automatic pre/post snapshots on Fedora 42, wiring it into DNF5 with the actions plugin, installing grub-btrfs so snapshots appear in the GRUB menu, and testing a real rollback by removing a package and restoring it. There’s also a comparison with openSUSE, which ships all of this out of the box. If you’re looking for a different backup strategy, see our guide on immutable backups with Restic and Borg.
Tested April 2026 on Fedora 42 (Cloud Edition, kernel 6.14.0-63.fc42.x86_64) with Snapper 0.11.0 and grub-btrfs 4.13
How Btrfs Snapshots Differ from LVM Snapshots
Both Btrfs and LVM can snapshot a filesystem, but the implementations are fundamentally different. Btrfs snapshots are metadata-only operations that complete instantly regardless of volume size. LVM snapshots allocate a separate COW device that degrades write performance proportionally to snapshot age. If you’re currently using LVM snapshots for backups, our LVM snapshot guide covers that workflow.
| Feature | Btrfs Snapshots | LVM Snapshots |
|---|---|---|
| Creation time | Instant (metadata-only) | Fast but requires COW device allocation |
| Performance impact | Negligible | Write penalty increases with snapshot age |
| Space usage | Shared blocks, grows only as data changes | Fixed-size COW device, overflows kill the snapshot |
| Rollback method | snapper undochange or boot into snapshot | lvconvert –merge (requires reboot) |
| Number of snapshots | Hundreds with minimal overhead | Each snapshot needs its own COW device |
| Granularity | Per-subvolume | Per-logical-volume |
| Built into filesystem | Yes | No (block-level, filesystem unaware) |
The practical advantage is that you can keep dozens of Btrfs snapshots with almost no performance cost, which makes automatic pre/post snapshots on every DNF transaction viable.
Fedora 42 Default Btrfs Layout
Fedora has shipped Btrfs as the default filesystem since Fedora 33. The installer creates three subvolumes on the root partition, each mounted at a different path. Understanding this layout matters because Snapper operates on subvolumes, not mount points.
List the existing subvolumes:
sudo btrfs subvolume list /
Fedora 42 creates three subvolumes by default:
ID 256 gen 21 top level 5 path root
ID 257 gen 14 top level 5 path home
ID 258 gen 22 top level 5 path var
Check how they’re mounted:
mount | grep btrfs
Each subvolume is mounted with compression and async discard enabled:
/dev/vda4 on / type btrfs (rw,relatime,seclabel,compress=zstd:1,discard=async,space_cache=v2,subvolid=256,subvol=/root)
/dev/vda4 on /home type btrfs (rw,relatime,seclabel,compress=zstd:1,discard=async,space_cache=v2,subvolid=257,subvol=/home)
/dev/vda4 on /var type btrfs (rw,relatime,seclabel,compress=zstd:1,discard=async,space_cache=v2,subvolid=258,subvol=/var)
The root subvolume (ID 256) holds /, which is where DNF installs packages. That’s the subvolume we’ll configure Snapper to snapshot. The home subvolume gets its own config for user data protection.
Install and Configure Snapper
Snapper is in the default Fedora repos. Install it along with the DNF5 actions plugin, which we’ll need later for automatic pre/post snapshots:
sudo dnf install -y snapper libdnf5-plugin-actions
Create a Snapper configuration for the root subvolume:
sudo snapper -c root create-config /
This creates the /.snapshots directory and a new Snapper config file at /etc/snapper/configs/root. Do the same for /home if you want user data snapshots:
sudo snapper -c home create-config /home
Verify both configs exist:
sudo snapper list-configs
Both subvolumes should appear:
Config │ Subvolume
───────┼──────────
home │ /home
root │ /
Enable Timeline Snapshots
Timeline snapshots are hourly automatic snapshots that Snapper creates and cleans up based on retention rules. Enable them for the root config:
sudo systemctl enable --now snapper-timeline.timer
sudo systemctl enable --now snapper-cleanup.timer
The timeline timer creates snapshots, and the cleanup timer deletes old ones according to the retention policy.
Set Retention Limits
The default retention is generous to a fault. On a server with limited disk, tighten it. Edit the root config:
sudo vi /etc/snapper/configs/root
Set these values (adjust to your disk size):
TIMELINE_CREATE=yes
TIMELINE_CLEANUP=yes
TIMELINE_LIMIT_HOURLY=5
TIMELINE_LIMIT_DAILY=7
TIMELINE_LIMIT_WEEKLY=0
TIMELINE_LIMIT_MONTHLY=0
SPACE_LIMIT=0.5
FREE_LIMIT=0.2
NUMBER_CLEANUP=yes
NUMBER_LIMIT=50
SPACE_LIMIT=0.5 means Snapper will delete snapshots if they consume more than 50% of the filesystem. FREE_LIMIT=0.2 triggers cleanup when free space drops below 20%. NUMBER_LIMIT=50 caps the total number of numbered snapshots (the pre/post type that DNF creates). These limits prevent snapshots from silently filling your disk.
SELinux Context for Snapshots
Fedora runs SELinux in enforcing mode. Snapper’s .snapshots directory needs the correct context, or restoring files from snapshots may fail with permission denied errors. The context is usually inherited correctly, but verify it:
ls -Zd /.snapshots
The context should show system_u:object_r:snapperd_data_t:s0. If it shows unlabeled_t instead, restore it:
sudo restorecon -rv /.snapshots
Take a manual test snapshot to confirm everything works:
sudo snapper -c root create -d "Manual test snapshot"
List snapshots to see it:
sudo snapper -c root list
Snapshot #1 should appear with your description and the current timestamp.
DNF5 Pre/Post Snapshots with the Actions Plugin
On openSUSE, zypper talks to Snapper natively. On Fedora, the old python3-dnf-plugin-snapper broke when Fedora 41 switched from DNF4 to DNF5. The replacement is the libdnf5-plugin-actions package, which lets you hook shell commands into DNF5 transaction events.
Create the actions file:
sudo vi /etc/dnf/libdnf5-plugins/actions.d/snapper.actions
Add the following content:
pre_transaction::::/usr/bin/sh -c echo\ "tmp.cmd=$(ps\ -o\ command\ --no-headers\ -p\ '${pid}')"
pre_transaction::::/usr/bin/sh -c echo\ "tmp.snapper_pre=$(snapper\ -c\ root\ create\ -c\ number\ -t\ pre\ -p\ -d\ '${tmp.cmd}')"
post_transaction::::/usr/bin/sh -c [\ -n\ "${tmp.snapper_pre}"\ ]\ &&\ snapper\ -c\ root\ create\ -c\ number\ -t\ post\ --pre-number\ "${tmp.snapper_pre}"\ -d\ "${tmp.cmd}"
The first line captures the DNF command that triggered the transaction. The second creates a pre-transaction snapshot and stores its number. The third line runs after the transaction completes and creates the matching post snapshot, linking it to the pre snapshot number. The -c number flag marks both snapshots for the number cleanup algorithm.
Test it by installing a package:
sudo dnf install -y htop
Check whether Snapper captured the transaction:
sudo snapper -c root list
The pre/post pair should appear with the exact DNF command as the description:
# │ Type │ Pre # │ Date │ User │ Cleanup │ Description │ Userdata
──┼────────┼───────┼─────────────────────────────────┼──────┼─────────┼──────────────────────┼─────────
0 │ single │ │ │ root │ │ current │
1 │ single │ │ Fri 03 Apr 2026 04:20:23 PM UTC │ root │ │ Manual test snapshot │
2 │ pre │ │ Fri 03 Apr 2026 04:21:05 PM UTC │ root │ number │ dnf install -y htop │
3 │ post │ 2 │ Fri 03 Apr 2026 04:21:05 PM UTC │ root │ number │ dnf install -y htop │
Snapshot #2 is the pre (filesystem state before htop was installed), and #3 is the post (state after). The Pre # column on #3 links it to #2, so Snapper knows they’re a pair.
Install grub-btrfs for Snapshot Boot
Rolling back from a running system with snapper undochange works well for package removals and config changes. But if a kernel update breaks boot entirely, you need to select a snapshot from the GRUB menu before the system even starts. That’s what grub-btrfs provides.
grub-btrfs is not in the Fedora repos, so build it from source. Install the build dependencies first:
sudo dnf install -y git make
Clone the repository and install:
git clone https://github.com/Antynea/grub-btrfs.git
cd grub-btrfs
sudo make install
Fedora uses grub2-mkconfig instead of grub-mkconfig, and the GRUB directory is /boot/grub2 instead of /boot/grub. Configure grub-btrfs for Fedora’s layout:
sudo vi /etc/default/grub-btrfs/config
Set these three variables:
GRUB_BTRFS_GRUB_DIRNAME="/boot/grub2"
GRUB_BTRFS_MKCONFIG=/usr/sbin/grub2-mkconfig
GRUB_BTRFS_SCRIPT_CHECK=grub2-script-check
Regenerate the GRUB configuration:
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
The output should list every snapshot it found:
Found snapshot: 2026-04-03 16:21:57 | root/.snapshots/5/snapshot | post | dnf install -y git make |
Found snapshot: 2026-04-03 16:21:55 | root/.snapshots/4/snapshot | pre | dnf install -y git make |
Found snapshot: 2026-04-03 16:21:05 | root/.snapshots/3/snapshot | post | dnf install -y htop |
Found 5 snapshot(s)
On reboot, a new “Fedora Linux snapshots” submenu appears in GRUB. Each snapshot is listed with its date and description. Selecting one boots a read-only view of the filesystem at that point in time, which is useful for recovering files or verifying what changed.
Enable the grub-btrfs daemon so new snapshots are automatically added to GRUB without running grub2-mkconfig manually each time:
sudo systemctl enable --now grub-btrfsd
The daemon watches /.snapshots for changes and regenerates the GRUB submenu automatically.
Break Something and Roll It Back
The real test of any snapshot system is recovering from damage. We’ll remove bash-completion (a package that deletes over a thousand files) and then undo the change using Snapper.
Remove the package:
sudo dnf remove -y bash-completion
The actions plugin creates snapshots #6 (pre) and #7 (post) automatically:
sudo snapper -c root list
The new pair appears at the bottom:
6 │ pre │ │ Fri 03 Apr 2026 04:26:30 PM UTC │ root │ number │ dnf remove -y bash-completion │
7 │ post │ 6 │ Fri 03 Apr 2026 04:26:30 PM UTC │ root │ number │ dnf remove -y bash-completion │
See exactly what changed between the pre and post snapshots:
sudo snapper status 6..7
Snapper shows every file that was deleted, modified, or created. The -..... prefix means deleted:
-..... /etc/bash_completion.d/000_bash_completion_compat.bash
-..... /etc/profile.d/bash_completion.sh
c..... /usr/lib/sysimage/libdnf5/nevras.toml
The full list is much longer (over a thousand deleted files). Now restore the pre-removal state:
sudo snapper undochange 6..7
Snapper restores every deleted file and reverts every modification:
create:1051 modify:8 delete:0
1,051 files recreated, 8 files reverted to their pre-removal state. The bash-completion package is back, and all its files are in place. No reboot required, no package manager involved. This is the power of filesystem-level rollback: it doesn’t care what removed the files or why. It just restores the snapshot.
One caveat: snapper undochange restores files on disk, but the RPM database still thinks the package is removed. Run dnf reinstall -y bash-completion afterward to sync the package manager’s state with reality. This is a Fedora-specific limitation because unlike openSUSE, the RPM database lives on a separate subvolume.
openSUSE: How It Compares
openSUSE Tumbleweed and Leap have had Snapper integration since 2012. If you’re choosing between Fedora and openSUSE specifically for snapshot rollback, the differences are worth knowing.
| Capability | Fedora 42 | openSUSE Tumbleweed/Leap |
|---|---|---|
| Snapper pre-installed | No (dnf install required) | Yes, configured out of the box |
| Package manager integration | Manual (actions plugin file) | Native zypper integration |
| GRUB snapshot boot | grub-btrfs from source | Built into grub2-snapper-plugin |
| Rollback command | snapper undochange | snapper rollback (full subvolume swap) |
| RPM database in snapshot | No (lives in /var, separate subvolume) | Yes (included in root snapshot) |
| Boot into snapshot + make permanent | Manual process | snapper rollback sets new default boot |
| YaST Snapper module | N/A | GUI for browsing and restoring snapshots |
| Default subvolume layout | 3 subvolumes (root, home, var) | 20+ subvolumes for granular control |
The biggest difference is rollback depth. On openSUSE, snapper rollback performs a true subvolume swap: it makes the snapshot the new default root and reboots into it. The RPM database, the kernel, everything rolls back together because they’re all in the same snapshot. On Fedora, snapper undochange is a file-level restore that doesn’t touch the bootloader or RPM database. Both approaches work, but openSUSE’s is more integrated.
That said, Fedora’s approach gives you more flexibility. You can selectively undo specific transactions without reverting everything that happened after them, which is useful when only one DNF operation caused problems.
Snapshot Space Management
Snapshots are cheap to create but expensive to keep. Every file modification after a snapshot is taken means the old data block is preserved instead of being freed. Over weeks, this adds up.
Snapper’s Cleanup Algorithms
Snapper has three cleanup algorithms that run via the snapper-cleanup.timer:
- number keeps only the most recent N snapshots of the numbered type (pre/post pairs from DNF). Controlled by
NUMBER_LIMIT - timeline thins out hourly snapshots over time, keeping more recent ones and fewer old ones. Controlled by
TIMELINE_LIMIT_HOURLY,TIMELINE_LIMIT_DAILY, etc. - empty-pre-post deletes pre/post pairs where nothing actually changed between them
The retention settings from earlier keep 5 hourly, 7 daily, and a max of 50 numbered snapshots. On a typical Fedora workstation or server that runs dnf update weekly, this translates to roughly 2 months of numbered snapshot history.
Check Actual Disk Usage
Standard df shows total Btrfs usage but doesn’t break down how much space snapshots consume. Use the Btrfs-specific command:
sudo btrfs filesystem usage /
Look at the “Used” line under “Data” to see actual consumption. For a per-snapshot breakdown, you technically need Btrfs quota groups (qgroups), but enabling qgroups on a production system is a bad idea. Qgroups cause significant performance degradation on filesystems with many snapshots because the kernel must track every block reference. The Btrfs developers themselves recommend avoiding qgroups unless you absolutely need per-subvolume accounting.
Manual Cleanup
If disk space is tight, delete specific snapshots by number:
sudo snapper -c root delete 2 3
This removes the pre/post pair from the htop install. To delete a range:
sudo snapper -c root delete 1-5
Deleted snapshots don’t free space immediately. Btrfs reclaims blocks asynchronously in the background. Run btrfs filesystem sync / to flush pending operations, then check usage again.
What Snapshots Don’t Replace
Btrfs snapshots are not backups. They live on the same physical disk as the original data. A disk failure takes out both the live filesystem and every snapshot. Use snapshots for fast rollback after bad updates or config changes. Use actual backups (Restic, Borg, rsync to a different machine) for disaster recovery.
Frequently Asked Questions
Can Snapper roll back a kernel update on Fedora?
snapper undochange can restore the previous kernel’s files, but the bootloader still points to the new kernel. For kernel rollback, boot into a pre-update snapshot via the GRUB menu (provided by grub-btrfs), then remove the broken kernel from the live snapshot. Alternatively, use grubby --set-default to switch the default kernel entry without touching snapshots.
Do snapshots slow down Btrfs?
Creating and deleting snapshots is nearly instant. The performance cost comes from keeping many old snapshots while the filesystem is actively written to, because Btrfs must preserve old data blocks instead of overwriting them. With the retention limits set earlier (50 numbered, 5 hourly, 7 daily), the overhead is negligible on most workloads. Thousands of snapshots on a heavily written filesystem is where you’d notice it.
Does snapper undochange work across subvolume boundaries?
No. Each Snapper config operates on one subvolume. On Fedora, /var is a separate subvolume, so changes to files under /var (like the RPM database) are not captured in the root config’s snapshots. This is why you need to run dnf reinstall after a file-level undo to sync the package database. On openSUSE, the subvolume layout is designed to avoid this problem.