About
The BTRFS file system has the ability to do snapshots. This can be very useful for backups. Rotating snapshots is an application of snapshots where snapshots are taken at regular intervals and older snapshots are removed. This could be over time periods of hours, months or even years.
There are other programs that perform rotating snapshots, including ones that work with BTRFS such as timeshift. However, their snapshots are hard to navigate as they are just kept as numbers rather than dates. Such systems are designed to restore entire snapshots rather than restoring individual files/directories.
This snapshot system is designed to make a directory structure using the date/time of the snapshot, thus making it easy to navigate the resulting snapshots. Used with its sister program BTRFS Snapshots Browser allows files and directory changes to be traced over multiple snapshots, making it easy to find changes.
Download
Starting in 2025, archives are signed by DrQue.net.
Archives signed by Andrew Que. Keys are generated yearly and can be found on the MIT PGP Public Key Server by doing a search for Andrew Que. They can also be downloaded directly from DrQue.net.
Version 1.0.2
Released June 24, 2025
- Bug fix for sending snapshots to destination over SSH.
- Download
- Version 1.0.2
- File size
- 31,938
- File date
- 2025-06-24 07:58:39.286
- CRC-32
f4c04cf4
- Source SHA256
59e067c8d025be18f17913d9ca87683d197a7a83cddd3c7449bd3bf0c1031a66
- PGP signature
- Source PGP signature
How it works
BTRFS Rotating Snapshots has three primary functions:
- Take a snapshot
- Prune snapshots
- Synchronize snapshots to a remote device (for backup)
Typically snapshots are setup to be taken at regular intervals (hourly, daily monthly) using a cron job.
Taking Snapshots
There are two ways to specify commands: with inline parameters, and using a configuration file.
root@system:/# btrfsSnapshots.py take /path/to/subvolume /path/to/snapshots root@system:/# btrfsSnapshots.py --config /path/to/config.json take
This function will take a snapshot of the subvolume
/path/to/subvolume
and place the snapshot subvolume in
/path/to/snapshots
.
Timestamp format
Snapshots are named with the time the snapshot was taken. The date
format is variable but typically is in the format YYYY-MM-DD
HH:MM:SS
.
The timestamp has three configuration variables:
date_format
- How the date is read.snapshot_name_format
- How the date is saved.date_regular_expression
- Regular expression to match a date
date_format
and snapshot_name_format
often match one
another. However, the snapshot name format might force some fields. For example,
%Y-%m-%d %H:%M:00
forces the second field to zero. This can be
desirable for cron jobs as they sometimes take a second or two to launch the
scheduled process.
Another common timestamp is to omit the time of day completely. For example,
%Y-%m-%d
could be used if snapshots are taken at most once a day.
The regular expression is used to get a directory listing of existing snapshots. Other than the snapshot, BTRFS Rotation Snapshots is stateless. So to find the existing snapshots (needed during pruning) the existing listing of the destination directory is used and matched against a regular expression.
One side effect is that snapshots not following the normal pattern are not removed. This can be useful for named snapshots. However, this too can be overridden.
Checking for changes
By default new snapshots are checked against the previous snapshot for
changes. If no changes have occurred, the new snapshot is removed. Checking
for changes uses rsync
.
Pruning Snapshots
Two forms of pruning: all configured by the command line, and from a configuration file.
root@system:/# btrfsSnapshots.py --keep-last-days=10 --keep-last-months=-1 prune /path/to/snapshots root@system:/# btrfsSnapshots.py --config /path/to/config.json prunePeriodically snapshots should be pruned so that unneeded snapshots are removed. There are four variables that control how long snapshots remain:
keep_last_snapshots
- Minimum number of most recent snapshots to keepkeep_last_days
- One snapshot for this many days.keep_last_months
- One snapshot for this many months.keep_last_years
- One snapshot for this many years
Configuration is saved in a JSON file. Anything that can be specified from the command line can be specified in the configuration file. Only difference is that underscores change to dashes.
{"keep_last_snapshots" : 11,
"keep_last_days" : 7,
"keep_last_months" : 5,
"keep_last_years" : 3
}
This configuration will yield a result like this:
root@system:/snapshots# ls -ldrwxr-xr-x 1 root root 54 Jan 1 00:00 '2019-12-31 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2020-12-31 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2021-12-31 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-05-31 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-06-30 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-07-31 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-08-31 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-09-30 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-20 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-21 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-22 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-23 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-24 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-25 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-26 17:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-27 15:15:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-27 15:30:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-27 15:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-27 16:00:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-27 16:15:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-27 16:30:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-27 16:45:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-27 17:00:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-27 17:15:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-27 17:30:00'
drwxr-xr-x 1 root root 54 Jan 1 00:00 '2022-10-27 17:45:00'
-rw-r--r-- 1 root root 32 Oct 27 17:45 lastSnapshot.txt
In this example snapshots are taken every 15 minutes. This is what remains after 5 years. The backgrounds are color coated:
keep_last_snapshots
keep_last_days
keep_last_months
keep_last_years
A value of 0 in one of the fields means no snapshots in that interval are kept. A value of -1 means keep all records. For example, -1 in months will keep one snapshot for every month. All settings after this are moot. So if months are set to -1, it doesn't matter what is put in year because all months are saved anyway.
Cron job
Generally this script will be run using a cron job. There are two different jobs, taking a snapshot and pruning the snapshots.
# Hourly snapshots0 * * * * /usr/local/sbin/btrfsSnapshots.py --config=/etc/btrfsSnapshot/subvolumeA.json take
0 * * * * /usr/local/sbin/btrfsSnapshots.py --config=/etc/btrfsSnapshot/subvolumeB.json take
# End-of-day snapshots
55 23 * * * /usr/local/sbin/btrfsSnapshots.py --config=/etc/btrfsSnapshot/subvolumeA.json take -f
# Prune snapshots
0 0 * * * /usr/local/sbin/btrfsSnapshots.py --config=/etc/btrfsSnapshot/subvolumeA.json prune
0 0 * * * /usr/local/sbin/btrfsSnapshots.py --config=/etc/btrfsSnapshot/subvolumeB.json prune
In the example above, hourly snapshots are taken on two subvolumes.
BTRFS Setup Example
While snapshots can be used to make images of an entire hard drive, subvolumes can be used to effectively make backup images of individual directories. Typically, this is done with directories that contain regular changing data, and data that you may want to have for backup purposes if it has been changed.
For this example, say we want to setup snapshots on the following sets of data:
- Documents
- Photos
- Recordings (music/video)
- Source (software source code)
Assume the root file system is BTRFS and none of these directories currently exist. If they do, rename them so they can be created as subvolumes.
Create the subvolumes.
root@system:/# btrfs subvolume create /home/user/Documents Create subvolume '/home/user/Documents'root@system:/# btrfs subvolume create /home/user/Photos Create subvolume '/home/user/Photos'
root@system:/# btrfs subvolume create /home/user/Recordings Create subvolume '/home/user/Recordings'
root@system:/# btrfs subvolume create /home/user/Source Create subvolume '/home/user/Source'
Now move your data into these directories. Once populate with data, you can use
BTRFS Rotating Snapshots on those subvolumes. You will just need a path to place
the snapshots. Some systems use a hidden snapshot directory like
~/.snapshots
. If not wanting to hide the snapshots you can use
~/snapshots
. To get the snapshots to show up as the first
item in a directory listing, use ~/@snapshots
. To make it
the last item, use ~/_snapshots
. For the example, we will
make a directory in root called @snapshots
.
Now to create a schedule for when to take snapshots.
- Documents
- Changes often. Snapshot every hour. Keep last 24 snapshots, the last 60 days, last 8 months for 1 year. Backup daily.
- Photos
- Snapshot once a day. Keep last 90 days, 12 months for all time. Backup weekly.
- Recordings (music/video)
- Snapshot once a day. Keep last 30 days, 1 month for all time. Backup monthly.
- Source (software source code)
- Changes often. Snapshot every 15 minutes. Keep last 24 snapshots, the last 20 days, last 6 months for 2 years. Backup weekly.
To accomplish this, we will create configuration files in /etc/btrfs
.
Configuration Files
Documents configuration
/etc/btrfs/documents.json
"name" : "Documents",
"source" : "/home/user/Documents",
"destination" : "/@snapshots/Documents",
"keep_last_snapshots" : 24,
"keep_last_days" : 60,
"keep_last_months" : 8,
"keep_last_years" : 1
}
Photos configuration
/etc/btrfs/photos.json
"name" : "Photots",
"source" : "/home/user/Pictures",
"destination" : "/@snapshots/Pictures",
"keep_last_snapshots" : 0,
"keep_last_days" : 90,
"keep_last_months" : 12,
"keep_last_years" : -1,
"snapshot_name_format" : "%Y-%m-%d",
"date_regular_expression" : "^(\\d{4}-\\d{2}-\\d{2})$"
}
Recordings configuration
/etc/btrfs/recordings.json
"name" : "Recordings",
"source" : "/home/user/Music",
"destination" : "/@snapshots/Music",
"keep_last_snapshots" : 0,
"keep_last_days" : 30,
"keep_last_months" : 1,
"keep_last_years" : -1,
"snapshot_name_format" : "%Y-%m-%d",
"date_regular_expression" : "^(\\d{4}-\\d{2}-\\d{2})$"
}
Source configuration
/etc/btrfs/source.json
"name" : "Source",
"source" : "/home/user/Source",
"destination" : "/@snapshots/Source",
"keep_last_snapshots" : 24,
"keep_last_days" : 20,
"keep_last_months" : 6,
"keep_last_years" : 2
}
Note how the snapshots that only take place daily have a specified date format. Both the date, and the regular expression are given to specify the date in year (4 characters), month and day. The default date format is year (4 characters), month, day, hour, minute and second. That is fine for periodic snapshots.
Cron jobs
Now the crontab
configuration.
Crontab
# Periodic Snapshots0 * * * * /opt/btrfsSnapshots.py --config=/etc/btrfs/documents.json take
*/15 * * * * /opt/btrfsSnapshots.py --config=/etc/btrfs/source.json take
# End-of-day snapshots
55 23 * * * /opt/btrfsSnapshots.py --config=/etc/btrfs/documents.json --snapshot-name-format '\%Y-\%m-\%d \%H:59:59' take -f
55 23 * * * /opt/btrfsSnapshots.py --config=/etc/btrfs/photos.json take -f
55 23 * * * /opt/btrfsSnapshots.py --config=/etc/btrfs/recordings.json take -f
55 23 * * * /opt/btrfsSnapshots.py --config=/etc/btrfs/source.json --snapshot-name-format '\%Y-\%m-\%d \%H:59:59' take -f
# Prune snapshots
0 0 * * * /opt/btrfsSnapshots.py --config=/etc/btrfs/documents.json prune
5 0 * * * /opt/btrfsSnapshots.py --config=/etc/btrfs/photos.json prune
10 0 * * * /opt/btrfsSnapshots.py --config=/etc/btrfs/recordings.json prune
15 0 * * * /opt/btrfsSnapshots.py --config=/etc/btrfs/source.json prune
# Backup snapshots
0 2 * * * /opt/btrfsSnapshots.py --config=/etc/btrfs/documents.json send /@snapshots/Documents /remote/@snapshots/Documents/ remote-computer 30 2 * 0 3 /opt/btrfsSnapshots.py --config=/etc/btrfs/photos.json send /@snapshots/Pictures /remote/@snapshots/Pictures/ remote-computer 0 3 * 1 * /opt/btrfsSnapshots.py --config=/etc/btrfs/recordings.json send /@snapshots/Music /remote/@snapshots/Music/ remote-computer 30 3 * * 0 /opt/btrfsSnapshots.py --config=/etc/btrfs/source.json send /@snapshots/Source /remote/@snapshots/Source/ remote-computer
The periodic snapshots just use the take
parameter.
Nothing more is needed.
End of the day snapshots have a forced take. That is, the snapshot is not removed even if nothing in the snapshot has changed. This is useful for having a record of end-of-day snapshots in your history. Notice how the documents and source force a snapshot name. In this case, the snapshot forced the minutes and seconds to be constant. Cron usually takes a second or two to start a job, and this forces the minutes and second of the timestamp to always remain the same.
Pruning snapshots takes place daily. This will remove old snapshots. It should be done after the daily snapshot has been taken. They are spread out with 5 minutes between them. This may not be necessary, but something to consider.
The backups are all sent to a remote computer called
remote-computer
, connected via SSH. Backups begin at 2:10
am. They are (and should be) staggered to account for network traffic,
especially if the receiving computer is not on an interanet. The
staggering time depends on the particular application. Here they are
just
User comments
Feel free to leave some feedback. A subset of Markdown is supported.
Opinions are not censored, but all advertisements will unceremoniously be deleted.
Comments are Javascript-based. Without Javascript enabled, this message is all that appears.
Copyright
BTRFS Rotating Snapshots is free, open-source software released under the GNU General Public License v3.
Author
BTRFS Rotating Snapshots is written and maintained by Andrew Que. To get in touch with Andrew Que, visit his contact page.