Configure Fedora with full root level snapshot support

A guide walking you through how to configure your Fedora installation to get full root snapshot and rollback support.

Configure Fedora with full root level snapshot support

Whether you've been introduced to Fedora by watching recent YouTube videos on it, have known about it for some time and it's finally time to give it a real try, or you've used it in the past and are eager to get back to all that it offers, this guide might be helpful to you if you're looking to set up BTRFS snapshots right away.

Fedora is a fantastic choice with a lot going on for itself. It's out of the box experience is wonderful, with most everything just working. This is a really nice choice for people who want their machine to work and don't want to spend potentially large amounts of time tinkering and bug fixing.

With that said, Fedora's out of the box BTRFS configuration unfortunately leaves a bit to be desired. This is what this guide sets out to solve.

Snapshots?

If you're less familiar with what snapshots are or how they can be used; they basically allow your system to make a moment-in-time capture of your drive or directory, allowing you to revert back to that state at a later point if needed. It does this without making a copy of everything, so unlike more traditional backup or clone type solutions, this won't actually fill up your drive super quick or take ages to complete.

BTRFS works by what it calls Copy-on-Write (delightfully abbreviated to CoW. Moo!), which means whenever writes occur it doesn't actually overwrite existing data, it instead creates a new modified copy of the data block elsewhere, and updates the metadata to point to that block instead. This is what BTRFS' snapshot functionality uses to allow for these moments in time to be captured very quickly and, at least initially, take up basically no additional space.

Installing Fedora

What we're going to walk through will be compatible with all more recent versions of Fedora. I have tested and used this with Fedora 34, 35, as-well as Fedora 36 – which is in beta at the time of this writing.

This guide assumes you are planning on installing Fedora as the only OS to a drive. If you are planning on dual booting with another OS, please ensure you adjust the necessary steps to accommodate this. It might be useful to follow a guide on that specific subject if needed.

With a Fedora live USB stick created, boot up your system and select the install Fedora option when prompted. After selecting your region/language, you'll be greeted with the following screen.

The "Installation Summary" screen of Fedora's installer, shown right after you made your language selection.

This might be one of the least intuitive parts of the Fedora installation, but they're apparently working on an updated installer that should remedy this. Regardless, unless you are planning on dual booting, the steps here are fortunately very straight-forward – UX oddities aside.

Click the Installation Destination option under the System header, in the following screen you'll be able to select which drive you'd like to install Fedora too. If you only have one drive, you might find that it is already selected. You can tell by the white-on-black check mark that appears on the drive.

The "Installation destination" view of Fedora's installer, with the first (and only) drive already selected.

By default the installer will have also selected the "Automatic" storage configuration option. Unless you need to customize things for a dual-boot setup or so, this is the easiest route to take.

In case your drive already has another OS installed, you should tick the "I would like to make additional space available" checkbox. This will let you remove existing partitions, after which the Fedora installer's "automatic" mode can create the necessary partitions.

Once you're ready, click the "Done" button hidden away in the top-right corner of the installer, and then proceed with the installation by clicking the start installation button.

This will take a little bit, after which your system should automatically reboot and bring you to the account setup, and final, step of the Fedora installation process. Follow these final steps until you are greeted with Fedora's nice and clean desktop.

Configuring BTRFS

With the installation done and your system booted into a nice and fresh Fedora installation, let's actually make the necessary changes to get our root-level snapshots working the way we want it. We'll also install and configure a tool that automatically creates snapshots before and after dnf upgrades, too.

In case you haven't already, it might be a good idea to update your system right away so everything is fully up-to-date.

For these next few steps we'll mostly be using terminal commands, so let's open up Terminal now.

By default Fedora's installation configures two BTRFS subvolumes; one for your root drive  (/), the other for your home directory (/home). You can confirm this by running the following command:

❯ sudo btrfs subvolume list / | grep "level 5"

You'll see something like this as the result:

❯ sudo btrfs subvolume list / | grep "level 5"
ID 256 gen 36 top level 5 path home
ID 257 gen 36 top level 5 path root

Installing Snapper

Let's proceed. We'll install a utility called snapper, along with its dnf plugin. Run the following command to install both:

❯ sudo dnf install snapper python-dnf-plugin-snapper

With snapper installed, let's create and configure snapshots for the root partition first. Run teh following command to do this:

❯ sudo snapper -c root create-config /

Creating a root-level .snapshots subvolume

Creating this configuration also creates a snapshots subvolume. However, this is created as as subvolume directly under the root partition, which we don't want. You can see what I mean by listing out the btrfs subvolumes again. You'll see something like this:

❯ sudo btrfs subvolume list /
ID 256 gen 43 top level 5 path home
ID 257 gen 58 top level 5 path root
ID 258 gen 25 top level 257 path var/lib/machines
ID 259 gen 58 top level 257 path .snapshots

Note the .snapshots volume with its level of 257. To correct this, let's delete this automatically created subvolume and create a new one that's at the same level as the root and home subvolumes. First, let's delete the subvolume:

❯ sudo btrfs subvolume delete /.snapshots

Now we can re-create it, but at the right level. As you noticed earlier, the default Fedora installation has two subvolumes. Even though we look at / as the root volume, in this case it actually exists in a subvolume of its own. The actual root of your drive isn't mounted directly, only the two subvolumes are.

In order for us to create another subvolume at this higher level, we need to temporarily mount the real root drive so we can run the appropriate commands from there.

There's several ways to do this. For this guide we'll use the drive's UUID. This is easily discoverable by listing out the contents of your system's fstab file, like so:

❯ cat /etc/fstab

The results will look something like this:

UUID=2271c46d-9093-4373-9b9f-4f4bac3f944f /                       btrfs   subvol=root,compress=zstd:1 0 0
UUID=a83793bc-31dc-4d79-b4b9-adadafdde13b /boot                   ext4    defaults        1 2
UUID=2271c46d-9093-4373-9b9f-4f4bac3f944f /home                   btrfs   subvol=home,compress=zstd:1 0 0

In my example –which is running inside a virtual machine– the main drive's UUID is 2271c46d-9093-4373-9b9f-4f4bac3f944f, you can see how both the / and /home subvolumes reference this same UUID. Take a look at your own system and make note of the primary drive's UUID. We'll need it for the next step.

Let's create a new empty directory where the snapshots will be mounted, as-well as a temporary directory to which we can mount the drive, and then mount the actual drive to it:

❯ sudo mkdir /mnt/btrfs /.snapshots
❯ sudo mount /dev/disk/by-uuid/2271c46d-9093-4373-9b9f-4f4bac3f944f /mnt/btrfs

Substitute the UUID with the one you found.

Now with the actual drive mounted, let's cd into it and create the new root-level snapshot subvolume:

❯ cd /mnt/btrfs
❯ sudo btrfs subvolume create snapshots

Let's confirm that everything looks alright by listing out all subvolumes. The results should look something like this:

❯ sudo btrfs subvolume list / | grep "level 5"
ID 256 gen 129 top level 5 path home
ID 257 gen 132 top level 5 path root
ID 259 gen 132 top level 5 path snapshots

With this done, we can unmount the root drive again, clean up after ourselves, and continue with the final bits of configuration.

❯ cd ~
❯ sudo umount /mnt/btrfs
❯ sudo rmdir /mnt/btrfs

Using your favorite text editor with sudo or root permissions, open up /etc/fstab and let's add the new subvolume. We do this by adding a new line to this file that references the same UUID we used just before, and references the newly created snapshots subvolume.

The easiest way is to just duplicate the line already in your fstab file for the home subvolume and changing the mount path as-well as subvol value. The end result should look something like this:

UUID=2271c46d-9093-4373-9b9f-4f4bac3f944f /                       btrfs   subvol=root,compress=zstd:1 0 0
UUID=a83793bc-31dc-4d79-b4b9-adadafdde13b /boot                   ext4    defaults        1 2
UUID=2271c46d-9093-4373-9b9f-4f4bac3f944f /home                   btrfs   subvol=home,compress=zstd:1 0 0
UUID=2271c46d-9093-4373-9b9f-4f4bac3f944f /.snapshots             btrfs   subvol=snapshots,compress=zstd:1 0 0

Save and close the file. We can now try to auto mount everything to make sure everything is working as it should by running:

❯ sudo systemctl daemon-reload
❯ sudo mount -a

Updating Grub

By default Fedora configures grub to simply reference the top level as the default subvolume. We need to change this to be able to support root subvolume rollbacks. First, let's check what the current configuration says:

❯ sudo btrfs subvolume get-default /
ID 5 (FS_TREE)

Recall when listing out the BTRFS subvolumes that we could see their respective IDs:

❯ sudo btrfs subvolume list / | grep "level 5"
ID 256 gen 129 top level 5 path home
ID 257 gen 132 top level 5 path root
ID 259 gen 132 top level 5 path snapshots

In my example's case, the root subvolume has an ID of 257. Check your system's IDs and once you've found the correct one for your root subvolume, update the default value with the following command:

❯ sudo btrfs subvolume set-default 257 /

Now when checking again, it should look something like this:

❯ sudo btrfs subvolume get-default /
ID 257 gen 143 top level 5 path root

Next we need to update the Grub configuration to not specifically reference the root subvolume by name. Fedora comes with a utility called grubby by default which seems to be the Fedora way of doing this. We want to remove this reference by name so that the default value we have just configured can do its thing:

❯ sudo grubby --update-kernel=ALL --remove-args="rootflags=subvol=root"

With that done, we should now be ready to enjoy root-level snapshots with the ability to rollback. Let's reboot now.

Screenshot showing Fedora 36 Beta running with a Terminal window open that lists the results of the `sudo snapper ls` command.

Now every time you install, update, or remove something through dnf, snapshots are automatically created before and after these actions. This includes anything you might install/update/remove through the Software Center GUI application – though not when installing flatpaks of course.

Here's an example of what my snapper ls results look like after installing 0 A.D.:

❯ sudo snapper ls
 # | Type   | Pre # | Date                            | User | Cleanup | Description              | Userdata
---+--------+-------+---------------------------------+------+---------+--------------------------+---------
0  | single |       |                                 | root |         | current                  |         
1  | pre    |       | Mon 18 Apr 2022 01:32:52 PM KST | root | number  | /usr/bin/dnf install 0ad |         
2  | post   |     1 | Mon 18 Apr 2022 01:33:07 PM KST | root | number  | /usr/bin/dnf install 0ad |         

You'll notice that each snapshot has an ID listed. If you ever need to roll back to a previous state, you can use that ID to pick the state to roll back to. For example, if I want to revert to the state just before installing 0 A.D., I could run the following:

❯ sudo snapper --ambit classic rollback 1

As snapshots are read-only by default, when rolling back snapper actually creates a new read-writeable snapshot based off of the snapshot you specified, and sets that as the new bootable subvolume. You can see this by listing out the snapshots after running the above command. It should look something like this:

❯ sudo snapper ls
 # | Type   | Pre # | Date                            | User | Cleanup | Description              | Userdata     
---+--------+-------+---------------------------------+------+---------+--------------------------+--------------
0  | single |       |                                 | root |         | current                  |              
1  | pre    |       | Mon 18 Apr 2022 01:32:52 PM KST | root | number  | /usr/bin/dnf install 0ad |              
2  | post   |     1 | Mon 18 Apr 2022 01:33:07 PM KST | root | number  | /usr/bin/dnf install 0ad |              
3  | single |       | Mon 18 Apr 2022 01:38:52 PM KST | root | number  | rollback backup          | important=yes
4+ | single |       | Mon 18 Apr 2022 01:38:52 PM KST | root |         | writable copy of #1      |

Now when you reboot, your system should be back to exactly what it looked like before installing the tool/module/thingy. I realize that my example of installing 0 A.D. wasn't a very useful use-case example, but you can imagine that this could be invaluable when installing something potentially unstable, or accidentally removing critical system level tools for example.

Adding snapshots for home, too

The way BTRFS snapshots work is that they do not include subvolumes inside other subvolumes when making snapshots, so your /home directory is not included in snapshots for /.

This is actually a good thing, as it allows us to configure our home directory snapshots separately and in exactly the way we want. What's more, it allows you to revert your system to an earlier snapshot without losing any files stored in your /home. Pretty neat, right?

Let's create another snapper config, this time for your home directory:

❯ sudo snapper -c home create-config /home

Let's add your user to the list of allowed users that are able to manage this configuration, so you don't have to use sudo when interacting with your home snapshots. Here we also enable the SYNC_ACL option which ensures file permissions are set to match whatever we configure through snapper for this particular configuration:

❯ sudo snapper -c home set-config SYNC_ACL=yes ALLOW_USERS=$USER

With that set, you should now be able to interact with snapper for your home directory without requiring sudo. Let's try creating a manual snapshot now:

❯ snapper -c home create --description "Hello, snapshot!"
❯ snapper -c home ls
 # | Type   | Pre # | Date                            | User       | Cleanup | Description      | Userdata
---+--------+-------+---------------------------------+------------+---------+------------------+---------
0  | single |       |                                 | root       |         | current          |         
1  | single |       | Mon 18 Apr 2022 02:07:50 PM KST | davejansen |         | Hello, snapshot! |         

Nice.

Scheduled snapshots

Depending on your personal preferences, you might want to have snapper automatically create scheduled snapshots too. By default a configuration has hourly snapshots set, which we probably don't want for the root volume at least. Let's disable this.

Disabling hourly snapshots for root

Open /etc/snapper/configs/root with your favorite text editor and sudo or root privileges, look for the TIMELINE_CREATE value, and set this to no, so it'll look like this:

# create hourly snapshots
TIMELINE_CREATE="no" 

Save your changes and close the file.

Customizing scheduled home snapshots

For your home directory, keeping hourly snapshots can have some nice benefits, so sticking with this default is probably a good thing. There are several additional settings you can tweak that control the number of hourly, daily, weekly, monthly, and yearly snapshots it wants to preserve. Keep in mind that the higher you set these numbers, the more space will be used by these snapshots over time. Here's one example of what this could look like:

# limits for timeline cleanup
TIMELINE_MIN_AGE="1800"
TIMELINE_LIMIT_HOURLY="12"
TIMELINE_LIMIT_DAILY="7" 
TIMELINE_LIMIT_WEEKLY="2"
TIMELINE_LIMIT_MONTHLY="6" 
TIMELINE_LIMIT_YEARLY="1"

Adjust these to your liking and save the file. Snapper will automatically pick up these changes.

Enabling scheduled snapshots and cleanup

In order for snapper to be able to run these scheduled tasks we need to enable the appropriate systemd timers:

❯ sudo systemctl enable --now snapper-timeline.timer
❯ sudo systemctl enable --now snapper-cleanup.timer

If you didn't enable any scheduled snapshots and just want the cleanup to happen automatically, you can enable only the second one.


Closing thoughts

We should now have a nice base Fedora setup with full snapshot and rollback support, even on the root level. While Fedora in general is a very stable experience –I've had absolutely no issues so far, it's such a pleasant experience!– there's always the possibility of a rogue tool or driver or configuration causing a ruckus. Having this extra layer of security is very nice for those kinds of cases.

I've been running this exact configuration on both my main work machine as-well as my laptop, and it's been working great. I must admit that I've not yet had to actually fix a broken system by reverting to a previous state as I've not had either system break, but it definitely helped having this around as a safety net, especially when I was doing things like testing out custom kernels and whatnot a while ago.

When a new release of Fedora comes out (like 36 will at any moment – at least at the time of writing), it's also nice to be able to upgrade your system and know that if anything breaks and either can't yet be fixed or you just don't have the time/interest to investigate, you're able to roll back to before the upgrade and continue with your working system, leaving that problem for another day.

I hope this is helpful to you. It know it might look a bit daunting with all these commands you have to run, but my hope is that I've written it out in an easy enough to follow way, with mostly copy/past-able commands. Of course I do hope that one day Fedora comes with a setup similar to this out of the box so you can just completely avoid having to configure such lower-level bits, but until that day comes, with just a little bit of elbow grease we're fortunately able to set it up ourselves.

I hope you'll enjoy your Fedora system!

Thank you.