A snapshot captures the state of a volume at one instant, using copy-on-write so it is created in seconds and only consumes space as the original data changes. LVM provides snapshots at the block device level for any filesystem, while Btrfs provides them at the filesystem level for individual subvolumes. In this tutorial you will create, mount and roll back LVM snapshots (classic and thin), create and restore Btrfs snapshots, send them to another disk as backups, and schedule them automatically with Snapper on Ubuntu 24.04.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS, such as a CubePath VPS, and a non-root user with
sudoprivileges. - For the LVM part: a volume group with free space for snapshots. This guide uses a volume group named
vg0with a logical volumedatamounted at/srv/data. Check yours withsudo vgsandsudo lvs. - For the Btrfs part: an empty extra disk or partition. This guide uses
/dev/vdc. Everything on it will be erased.
Snapshots protect against mistakes such as a bad upgrade or a deleted file, but they live on the same disks as the original data. They are not a replacement for backups on a different machine.
Part 1: LVM snapshots
Step 1 - Checking free space in the volume group
A classic LVM snapshot needs its own space in the volume group, where LVM stores the original version of every block that changes after the snapshot is taken. Check how much is free:
sudo vgs vg0
VG #PV #LV #SN Attr VSize VFree
vg0 1 1 0 wz--n- <50.00g <30.00g
Size the snapshot for the amount of data you expect to change while it exists, not for the full volume. If changes exceed the snapshot size, the snapshot becomes invalid and is useless. For a snapshot kept a few hours around an upgrade, 10 to 20 percent of the origin is usually enough.
Step 2 - Creating a snapshot
Create a 5 GB snapshot of vg0/data named data_snap:
sudo lvcreate --snapshot --size 5G --name data_snap vg0/data
Logical volume "data_snap" created.
For a consistent snapshot of a database, stop the database or flush and lock it first. A snapshot of a running database is only crash-consistent.
List the volumes. The Origin column shows what the snapshot belongs to and Data% shows how much of its space is already used:
sudo lvs vg0
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
data vg0 owi-aos--- 20.00g
data_snap vg0 swi-a-s--- 5.00g data 0.01
Step 3 - Mounting the snapshot to read or back up files
A snapshot is a regular block device, so you can mount it read-only to recover a file or to run a backup of a frozen view of the data while the original stays in use:
sudo mkdir -p /mnt/data_snap
sudo mount -o ro /dev/vg0/data_snap /mnt/data_snap
ls /mnt/data_snap
If the filesystem is XFS, add nouuid to the mount options (-o ro,nouuid), because XFS refuses to mount two filesystems with the same UUID.
For example, to archive the frozen view:
sudo tar -czf /root/data-$(date +%F).tar.gz -C /mnt/data_snap .
Unmount it when you are done:
sudo umount /mnt/data_snap
Step 4 - Letting LVM extend snapshots automatically
To avoid a snapshot filling up and becoming invalid, LVM can grow it automatically when it crosses a threshold. Open the LVM configuration:
sudo nano /etc/lvm/lvm.conf
In the activation section, set these two options (they exist commented out; uncomment and edit them):
activation {
snapshot_autoextend_threshold = 70
snapshot_autoextend_percent = 20
}
With these values, a snapshot that reaches 70 percent usage is grown by 20 percent of its size, as long as the volume group has free space. The work is done by the LVM monitoring daemon, so make sure it is running:
systemctl status lvm2-monitor --no-pager
● lvm2-monitor.service - Monitoring of LVM2 mirrors, snapshots etc. using dmeventd or progress polling
Loaded: loaded (/usr/lib/systemd/system/lvm2-monitor.service; enabled; preset: enabled)
Active: active (exited) since ...
Step 5 - Rolling back to the snapshot
Merging a snapshot back into its origin reverts the volume to the state it had when the snapshot was created, and removes the snapshot. All changes made since then are lost.
Unmount the origin so the merge can start immediately:
sudo umount /srv/data
sudo lvconvert --merge vg0/data_snap
Merging of volume vg0/data_snap started.
vg0/data: Merged: 100.00%
Mount the volume again and verify the data is back to the snapshot state:
sudo mount /srv/data
sudo lvs vg0
The data_snap volume is no longer listed. If the origin cannot be unmounted, for example because it is the root filesystem, lvconvert --merge schedules the merge and it runs the next time the volume is activated, which for the root filesystem means the next reboot.
If you do not need to roll back, simply remove the snapshot:
sudo lvremove vg0/data_snap
Step 6 - Using thin snapshots
Classic snapshots slow down writes to the origin and each one needs space reserved in advance. Thin provisioning avoids both: volumes and snapshots allocate blocks from a shared pool on demand, and you can keep many snapshots cheaply. The trade-off is that you must monitor the pool, because a full pool stops all writes to its volumes.
Create a 20 GB thin pool and a 10 GB thin volume inside it, then format and mount it:
sudo lvcreate --type thin-pool --size 20G --name pool0 vg0
sudo lvcreate --thin --virtualsize 10G --name thindata vg0/pool0
sudo mkfs.ext4 /dev/vg0/thindata
sudo mkdir -p /srv/thindata
sudo mount /dev/vg0/thindata /srv/thindata
A snapshot of a thin volume is created without a size, because it takes space from the pool:
sudo lvcreate --snapshot --name thindata_snap1 vg0/thindata
Thin snapshots are created with the "activation skip" flag, so they are not active by default. Activate it with --ignoreactivationskip (short form -K) before mounting:
sudo lvchange --activate y --ignoreactivationskip vg0/thindata_snap1
sudo mount -o ro /dev/vg0/thindata_snap1 /mnt/data_snap
Watch pool usage with Data% and Meta% and extend the pool before it approaches 100 percent:
sudo lvs -o lv_name,pool_lv,origin,lv_size,data_percent,metadata_percent vg0
LV Pool Origin LSize Data% Meta%
pool0 20.00g 6.12 10.94
thindata pool0 10.00g 12.20
thindata_snap1 pool0 thindata 10.00g 12.20
sudo lvextend --size +10G vg0/pool0
Rolling back works the same way as with classic snapshots: unmount the origin and run sudo lvconvert --merge vg0/thindata_snap1.
Part 2: Btrfs snapshots
Step 7 - Creating a Btrfs filesystem with subvolumes
Btrfs snapshots work on subvolumes, independent directory trees inside one filesystem. Keeping data in a subvolume, and snapshots in another, makes rollback clean. Install the tools and create the filesystem on the spare disk:
sudo apt install btrfs-progs
sudo mkfs.btrfs -L pool /dev/vdc
Mount the top level of the filesystem, and create one subvolume for data and one to hold snapshots:
sudo mkdir -p /mnt/pool
sudo mount /dev/vdc /mnt/pool
sudo btrfs subvolume create /mnt/pool/@data
sudo btrfs subvolume create /mnt/pool/@snapshots
Mount the data subvolume where applications use it:
sudo mkdir -p /srv/btrdata
sudo mount -o subvol=@data /dev/vdc /srv/btrdata
To make both mounts permanent, add them to /etc/fstab using the filesystem UUID from sudo blkid /dev/vdc:
sudo nano /etc/fstab
UUID=your_fs_uuid /mnt/pool btrfs subvolid=5,noatime 0 0
UUID=your_fs_uuid /srv/btrdata btrfs subvol=@data,noatime 0 0
Test the entries without rebooting:
sudo umount /srv/btrdata /mnt/pool
sudo mount -a
findmnt -t btrfs
TARGET SOURCE FSTYPE OPTIONS
/mnt/pool /dev/vdc btrfs rw,noatime,...,subvolid=5,subvol=/
/srv/btrdata /dev/vdc[/@data] btrfs rw,noatime,...,subvol=/@data
Step 8 - Taking and browsing snapshots
Create a read-only snapshot of @data. Read-only snapshots cannot be modified by accident, and they are required for btrfs send:
sudo btrfs subvolume snapshot -r /mnt/pool/@data /mnt/pool/@snapshots/data-$(date +%F-%H%M)
Create a readonly snapshot of '/mnt/pool/@data' in '/mnt/pool/@snapshots/data-2026-09-25-1030'
List only snapshots:
sudo btrfs subvolume list -s /mnt/pool
A snapshot is just a directory, so restoring a single file is a copy:
sudo cp -a /mnt/pool/@snapshots/data-2026-09-25-1030/app/config.yml /srv/btrdata/app/config.yml
Step 9 - Rolling back a whole subvolume
To revert all of @data to a snapshot, replace the subvolume with a writable snapshot of the saved one. Stop whatever is using /srv/btrdata first, then:
sudo umount /srv/btrdata
sudo mv /mnt/pool/@data /mnt/pool/@data.broken
sudo btrfs subvolume snapshot /mnt/pool/@snapshots/data-2026-09-25-1030 /mnt/pool/@data
sudo mount /srv/btrdata
Because the mount uses subvol=@data by name, it now mounts the restored copy. Once you have checked everything is correct, delete the old version:
sudo btrfs subvolume delete /mnt/pool/@data.broken
Step 10 - Sending snapshots to another disk as a backup
btrfs send serializes a read-only snapshot into a stream, and btrfs receive recreates it on another Btrfs filesystem, for example a second disk mounted at /backup or a remote server over SSH. After the first full transfer, you only send the differences between two snapshots.
Send the first snapshot in full:
sudo btrfs send /mnt/pool/@snapshots/data-2026-09-25-1030 | sudo btrfs receive /backup
The next day, take a new snapshot and send only what changed, using the previous snapshot as parent (-p). The parent must exist on both sides:
sudo btrfs subvolume snapshot -r /mnt/pool/@data /mnt/pool/@snapshots/data-2026-09-26-1030
sudo btrfs send -p /mnt/pool/@snapshots/data-2026-09-25-1030 \
/mnt/pool/@snapshots/data-2026-09-26-1030 | sudo btrfs receive /backup
To send to another server instead, pipe into SSH, for example | ssh your_user@backup_host sudo btrfs receive /backup.
Verify the snapshots arrived:
sudo btrfs subvolume list /backup
Step 11 - Automating snapshots with Snapper
Snapper creates snapshots on a schedule and deletes old ones according to retention limits, so you do not need your own scripts. Install it:
sudo apt install snapper
Create a configuration named data for the mounted subvolume. Snapper creates a .snapshots subvolume inside it to store its snapshots:
sudo snapper -c data create-config /srv/btrdata
Set how many hourly, daily, weekly and monthly snapshots to keep:
sudo snapper -c data set-config "TIMELINE_CREATE=yes" "TIMELINE_CLEANUP=yes" \
"TIMELINE_LIMIT_HOURLY=24" "TIMELINE_LIMIT_DAILY=7" \
"TIMELINE_LIMIT_WEEKLY=4" "TIMELINE_LIMIT_MONTHLY=3" "TIMELINE_LIMIT_YEARLY=0"
Snapshots are created and cleaned up by two systemd timers. Enable them:
sudo systemctl enable --now snapper-timeline.timer snapper-cleanup.timer
Take a manual snapshot before a risky change, make a change, and let Snapper show you what differs:
sudo snapper -c data create --description "before config change"
echo "debug: true" | sudo tee -a /srv/btrdata/app/config.yml
sudo snapper -c data list
# | Type | Pre # | Date | User | Cleanup | Description | Userdata
---+--------+-------+--------------------------+------+----------+----------------------+---------
0 | single | | | root | | current |
1 | single | | Fri Sep 25 10:00:01 2026 | root | timeline | timeline |
2 | single | | Fri Sep 25 10:42:13 2026 | root | | before config change |
sudo snapper -c data status 2..0
c..... /srv/btrdata/app/config.yml
Revert the changes between snapshot 2 and the current state:
sudo snapper -c data undochange 2..0
Troubleshooting
Insufficient free space when creating an LVM snapshot. The volume group has no free extents. Add a disk with pvcreate and vgextend, or use a smaller --size.
An LVM snapshot shows Data% at 100 and cannot be mounted. It overflowed and is invalid. Remove it with lvremove, then create a larger one or enable autoextend (Step 4).
ERROR: not a btrfs filesystem from btrfs receive. The destination must itself be Btrfs. Format the backup disk with mkfs.btrfs first.
btrfs send -p fails with cannot find parent subvolume. The parent snapshot was deleted on the receiving side. Send a new full snapshot without -p and use it as the new parent.
Conclusion
You can now take LVM snapshots of any filesystem, use thin snapshots when you need many of them, roll volumes back with a single merge, and manage Btrfs snapshots with rollback, incremental send/receive backups and automatic scheduling through Snapper. As next steps, take a snapshot automatically before package upgrades, send Btrfs snapshots to a second server over SSH, and monitor thin pool usage with your alerting system.
