Files

8.6 KiB

title, description
title description
BTRFS Snapshots Format Debian's root filesystem with BTRFS and use Snapper to instantly roll back a risky update or config change, as a local complement to Backrest's off-site 3-2-1 backups.

:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}

BTRFS is a Linux filesystem with one killer feature for a homelab: snapshots. A snapshot freezes the exact state of a filesystem in an instant, at essentially zero cost, without copying a single byte of data upfront. Break something ten minutes after an apt full-upgrade, or overwrite the wrong .env file, and you can go back to exactly how things were before, without touching a backup at all.

This is not what Backrest and the 3-2-1 rule are for, so it's worth being precise about the difference before setting anything up.

Snapshots are not backups

A snapshot lives on the exact same disk as the data it protects. It's instant, needs no network, and it's perfect for undoing a mistake you just made, but it does nothing at all the day that disk itself dies, gets stolen, or your server burns down. That's what a real 3-2-1 backup is for: a copy on different media, ideally off-site.

Think of it this way:

  • Snapshot = an undo button. Instant, local, cheap, only useful while the disk is alive.
  • Backup = insurance. Slower, off-site, the only thing that survives the disk itself failing.

Keep both. Snapshots make you fearless about updates and experiments, backups make sure a dead drive stays an inconvenience instead of a catastrophe.

::note{to="/serveex/core/installation#partitioning"}

This guide assumes the root partition was formatted with Btrfs during Debian's installation. Btrfs can't be safely bolted onto an existing ext4 root after the fact, so this is a choice you make once, at install time. ::

Formatting the root partition with BTRFS

Debian's Guided partitioning only ever offers ext4. To get Btrfs, pick Manual partitioning instead, at the same step the main install guide describes:

::steps{level="3"}

Select Manual partitioning

At the partitioning method screen, choose Manual instead of Guided - use entire disk.

Create a partition table

Select the disk, confirm creating a new empty partition table, then select the resulting FREE SPACE and choose Create a new partition.

Set the partition size and type

Give the partition the rest of the disk (minus a small EFI partition if you're on UEFI, handled the same way as a Guided install), and set Use as to Btrfs journaling file system, with the mount point /.

Finish partitioning

Finish partitioning and write changes to disk, then confirm with Yes.

Done!

::

Everything else in the installer stays the same.

Installing Snapper

Rather than juggling raw btrfs subvolume commands by hand, Snapper is a small tool that manages the whole snapshot lifecycle: creating them, storing them tidily, pruning old ones, and automatically bracketing every apt operation with a snapshot pair.

sudo apt install snapper
sudo snapper -c root create-config /

create-config registers a config named root for the / filesystem, and creates a dedicated .snapshots subvolume under it to store every snapshot, locked down to root. No @/@home subvolume split needed, this works fine on the plain single-subvolume layout the manual partitioning above just created.

Turn the config on by editing /etc/default/snapper:

SNAPPER_CONFIGS="root"

Then enable the timers that run Snapper's periodic snapshots and cleanup:

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

snapper-timeline.timer fires every hour by default, that's fixed by the unit itself, not by a config option. Each run takes one snapshot and files it under the appropriate bucket (hourly, daily, monthly, yearly); the TIMELINE_LIMIT_* settings covered further down only control how many of each bucket survive, not how often a snapshot is taken. snapper-cleanup.timer runs once a day (plus once 10 minutes after boot) to enforce those limits.

::tip To take timeline snapshots at a different frequency, for example every 6 hours instead of every hour, override the timer instead of editing the package's unit file directly:

sudo systemctl edit snapper-timeline.timer
[Timer]
OnCalendar=
OnCalendar=*-*-* 0/6:00:00

The empty OnCalendar= line clears the packaged hourly value first, since systemd otherwise adds new OnCalendar lines to the existing ones instead of replacing them. Apply it with sudo systemctl daemon-reload && sudo systemctl restart snapper-timeline.timer. ::

::note

Debian's package also ships an APT hook (/etc/apt/apt.conf.d/80snapper) that kicks in the moment a config is listed in SNAPPER_CONFIGS: from now on, every apt install, apt upgrade or apt full-upgrade automatically gets a snapshot right before and right after it runs, with zero extra effort on your part. ::

Taking a snapshot

For anything outside apt, like editing a systemd unit or a Docker Compose file, take one yourself with a description that will actually mean something later:

sudo snapper create --description "before compose change on swag"

List every snapshot with:

sudo snapper list
 # | Type   | Pre # | Date                     | Description
---+--------+-------+--------------------------+---------------------------------
0  | single |       |                          | current
1  | single |       | Tue 09 Sep 2026 18:30:00 | before compose change on swag
2  | pre    |       | Tue 09 Sep 2026 19:00:01 | apt install unattended-upgrades
3  | post   | 2     | Tue 09 Sep 2026 19:00:14 | apt install unattended-upgrades

Restoring files from a snapshot

Snapper keeps the full state of the filesystem at snapshot time under /.snapshots/<number>/snapshot, browsable like any other folder:

ls /.snapshots/1/snapshot/etc/ssh/

To see exactly what changed between a snapshot and the live system (0 always means "current") before touching anything:

sudo snapper status 1..0

That prints every file created, modified or deleted since. To undo those changes automatically:

sudo snapper undochange 1..0

Or restore a single file by hand, which is often the safer, more surgical choice:

sudo cp -a /.snapshots/1/snapshot/etc/ssh/sshd_config /etc/ssh/sshd_config

::note

This covers the vast majority of real homelab accidents: a bad config, a deleted file, a package upgrade that broke one thing. Snapper can also rollback the entire root filesystem to an earlier snapshot (it creates a new default subvolume and boots into it on the next restart), for a system in a genuinely bad state rather than just missing one file. It's a more delicate, less common operation, worth testing once on a machine you don't mind rebooting before you actually need it for real. And if the disk itself is the problem, that's exactly the scenario your off-site Backrest backup is for anyway. ::

Managing and pruning snapshots

Snapshots are cheap, not free: the moment the live filesystem diverges from one, the old blocks it still references stick around. Left unchecked, months of snapshots on a server that changes a lot (container images, logs) can quietly eat real disk space.

The snapper-cleanup.timer enabled above already prunes automatically, based on the config at /etc/snapper/configs/root:

Setting Default What it does
TIMELINE_LIMIT_HOURLY / _DAILY / _MONTHLY / _YEARLY 10 How many timeline snapshots of each granularity to keep
TIMELINE_LIMIT_WEEKLY 0 Off by default
NUMBER_LIMIT 50 How many manual (single) snapshots to keep

Adjust these to taste, then delete one immediately by hand if you need the space back right now:

sudo snapper delete 1

Check actual disk usage with df -h /: the numbers above only cap how many snapshots exist, not the space a very active server can still burn through between cleanups.

That's it: a rolling, automatic undo button for your server, quietly bracketing every update and pruning itself, running alongside the real backups Backrest is already taking off-site.