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.

💡
This guide has been slightly updated at 2023-05-29 with some additional notes the /boot partition and potential caveats of restoring to (very) old snapshots with kernel versions no longer present your system, and a change in my personal usage of the setup this guide describes.

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 permissions, open up /etc/fstab and let's add the new sub-volume. 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 sub-volume.

The easiest way is to just duplicate the line already in your fstab file for the home sub-volume 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

(Optional) Creating additional sub-volumes as desired

While this article only specifically covers how to set up root-level snapshot support, it might be worth considering setting up additional sub-volumes while you're at it.

For example, if you use Docker, creating a sub-volume and mounting it to /var/lib/docker would prevent root snapshots from filling up with docker volume data and also ensure that if you do roll back your root to a previous state, you don't lose anything related to your docker containers when doing so.

If you'd like to do this, you can effectively follow the same steps listed above, but instead create a sub-volume named something like docker, and adding a row in your fstab file that mounts it to the path mentioned above.

If you already have existing running containers and volumes there, you can first temporarily disable Docker, temporarily move all contents of the btrfs folder elsewhere, then create the sub-volume as described above, and after mounting it move everything from the btrfs folder you temporarily moved just before back.

Another example might be if you install Steam games on your root drive. You could create a sub-volume specifically for the ~/.local/share/Steam directory (assuming you are using a system-installed Steam – the path will be different if you use the Flatpak version for example), ensuring that reverting to a previous snapshot of your home directory won't make you lose your already downloaded games.

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 before we do anything else.

While the result of this guide is that you indeed have root level snapshot support, I recently realized thanks to a question from reader reaching out that the /boot partition (and with it /boot/efi) are not included in these snapshots, as these are not formatted using BTRFS.

I'm sure there would be a way to use pre/post hooks to copy contents from these volumes into and from a backup directory that could then be part of BTRFS snapshots, but that is a bit outside the scope of this guide. This is more involved than I think is worth it, as it would only really serve to allow you to restore (very) old snapshots that run kernel versions that have since been removed.

By default Fedora is set up to keep up to three Kernel versions installed. This can be modified to your liking by changing the installonly_limit value in /etc/dnf/dnf.conf, but keep in mind that by default your /boot partition is only assigned about 1GB of space.

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.

When a new release of Fedora comes out, 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 ran this exact configuration on both my main machine as-well as my laptop, and it was working great while I was using it. For the past 6 or 8 months or so I have not used this particular setup anymore, as I have personally fully moved over to using Fedora Silverblue on all my systems, where you basically get this out of the box, albeit in a very different way of course.

If you are using Workstation and followed along with this guide, I do hope this has been 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.

I know that one day this guide will no longer be necessary as Fedora's longer term plans are to effectively move completely over to a Silverblue-esque immutable file system approach. But for now, at least, you'll be able to enjoy Workstation with root-level snapshots support.

I hope you'll enjoy your Fedora system!

Thank you.