btrbk, btrfs snapshots and incremental backup in a single Perl script
Tool for creating snapshots and remote backups of btrfs subvolumes
At a glance
- What is it?
- btrbk takes atomic snapshots of btrfs subvolumes and sends them incrementally to local or remote destinations, driven entirely by one configuration file and a retention policy expressed as a minimum age plus a snapshot count. It is one Perl file with no dependencies beyond btrfs-progs, and its documentation is unusually good about the details that break a backup, including the snapshot directory it will not create for you.
- Who is it for?
- Use btrbk when your data already lives on btrfs and you want snapshots that are cheap, atomic and sendable over ssh without maintaining a second copy of your data somewhere else. Do not reach for it to back up a machine that is not btrfs, and read the retention section twice before you trust the numbers, because snapshot_preserve_min and snapshot_preserve mean different things and getting them wrong silently deletes history.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 74 days ago.
- What is it written in?
- Mainly Perl, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One Perl file, and the shortest install in this article
btrbk is a backup tool for btrfs subvolumes that takes advantage of btrfs-specific capabilities to create atomic snapshots and transfer them incrementally to backup locations. The distribution shape is unusual and worth stating first, because it removes almost every reason people hesitate to try a tool like this.
It is a single Perl script. The README says it does not require any special installation procedures or libraries, and the whole installation is three commands:
wget https://raw.githubusercontent.com/digint/btrbk/master/btrbk
chmod +x btrbk
sudo ./btrbk ls /That last command lists what btrbk sees on the root subvolume, which is a better first check than anything else: it confirms the script runs, that it can read your btrfs filesystem information, and that your privileges are sufficient. For a tool that deletes old snapshots on a schedule, that ordering is the right one.
The prerequisites are correspondingly short. You need btrfs-progs at version 4.12 or later, a Perl interpreter, which the README notes you probably already have, OpenSSH if you are transferring to or from a remote location, and mbuffer if you want rate limiting and progress bars. mbuffer is the only one that is optional in a way that changes what you see rather than what works, since without it a long transfer gives you no progress indication and no ability to cap bandwidth.
The tool is designed to be triggered two ways, and the split matters for how you set it up. It runs as a cron job for periodic snapshots and backups, and it runs from the command line for creating an additional snapshot on demand. Everything below follows from those two modes: the configuration is designed to be edited once and then left alone, and every command has a dry-run form so you can check the effect of a change without touching the disks.
What atomic snapshots and incremental send actually buy you
The feature list is long, and several items are the reason a btrfs-specific tool exists at all rather than a general-purpose script with btrfs-specific options.
Atomic snapshots mean the snapshot is a consistent point in time. Because a btrfs snapshot is a subvolume reference rather than a copy, creating one is a metadata operation and the data on disk is not duplicated, so a snapshot of a large subvolume is close to instantaneous and costs almost nothing until something is written into it. That is what makes hourly snapshots on a laptop practical, and it is also what makes the retention model cheap enough to keep months of history.
Incremental transfer is the second half. Rather than re-sending a whole subvolume, btrbk sends only what changed since the previous snapshot on the target, using btrfs send and receive semantics. The README also lists the related reporting features that make this auditable: listing file changes between backups, resolving and tracing btrfs parent-child and received-from relationships, and calculating accurate disk space usage based on block regions rather than apparent file size. That last one is not a nicety, because a btrfs volume with snapshots and compression will report sizes that have nothing to do with the bytes you would need to restore.
Two features exist specifically for the awkward cases. Wildcard subvolumes are described as useful for docker and lxc containers, which is a real problem: a container runtime creates a subvolume per container, and a static configuration listing them does not scale. And recovery from interrupted backups is called out for removable and mobile devices, which is the case where the sending side disappears mid-transfer and the target is left with a partial send.
The encryption feature is the one that extends the scope beyond btrfs. Encrypted backups to non-btrfs storage means the target does not have to be a btrfs filesystem at all, which converts a tool that looked btrfs-only into something that can write to a plain external disk. The repository's `contrib` directory reinforces that the ecosystem around the tool is broader than the script: there is a PBKDF2 key derivation helper written in Python, a migration script for converting raw send streams to a sidecar format, and a tool for restoring raw streams.
snapshot_preserve_min and snapshot_preserve mean different things
The retention model is the part of btrbk most likely to be configured incorrectly, and the README explains it well enough that there is no excuse for getting it wrong.
The simplest configuration in the README looks like this:
snapshot_preserve_min 18h
snapshot_preserve 48h
timestamp_format longThe README's explanation of what those mean is precise. All snapshots are preserved for at least 18 hours, which is `snapshot_preserve_min`, and this applies whether they were created by the cron job or manually by calling btrbk on the command line. Additionally, 48 hourly snapshots are preserved, which is `snapshot_preserve`.
So the two keys do different jobs. The minimum is a floor on age, and it is unconditional, which is what stops a manual `btrbk run` from immediately deleting snapshots the cron job created. The count is a retention window expressed in units of the configured `timestamp_format`, so with an hourly cron job and `48h` you get two days of hourly history, and the same setting with a daily cron job would give you two days of daily history, which is two snapshots.
The larger example in the README shows the layered form, and the layering is where this tool earns its keep. A laptop backup policy keeps all snapshots for 2 days regardless of frequency, keeps daily snapshots for 14 days, keeps daily backups for 20 days, keeps weekly backups for 10 weeks, and keeps monthly backups forever. The README's comment on the 14-day line is that it is very handy if you are on the road and the backup disk is not attached, which is the reason the `no` value for `target_preserve_min` appears alongside it: when the disk is absent, snapshots accumulate and the preservation targets are simply deferred.
The consequence of getting this wrong is silent and irreversible. btrbk prunes what it considers redundant, so an over-aggressive count deletes history that no other copy holds. Test the policy with a dry run before you let cron own it.
volume is a base specifier, and the snapshot directory is your problem
The configuration language has a small number of concepts and one trap that will stop a first run.
The trap is this: btrbk does not create subdirectories by default, and the snapshot directory must be created manually. The README is explicit about it, and gives the command:
# mkdir /mnt/btr_pool/btrbk_snapshotsIf you skip that, the first `btrbk run` has nowhere to put a snapshot. This is the correct default for a tool whose job is to manage existing storage, and it is also the first thing to check when nothing happens.
The other concept worth understanding is the `volume` section, which the README describes as merely a specifier for a base directory that can be skipped if you prefer to configure everything with absolute paths. The volume form is:
volume /mnt/btr_pool
snapshot_dir btrbk_snapshots
subvolume homeand the equivalent absolute-path form is `snapshot_dir /mnt/btr_pool/btrbk_snapshots` with `subvolume /mnt/btr_pool/home`. If you would rather not mount the btrfs root filesystem at a pool directory at all, the README shows the third form, with `snapshot_dir /btrbk_snapshots` and `subvolume /home`.
That raises the subvolid question. The examples assume the subvolume containing `home` and `rootfs` is mounted at `/mnt/btr_pool`, which is usually the btrfs root subvolume and always has `subvolid=5`. The README recommends mounting subvolid=5 if you want to back up your root filesystem, and adds that it was mandatory for btrbk before version 0.32.0. The fstab line is:
/dev/sda1 /mnt/btr_pool btrfs subvolid=5,noatime 0 0One more naming caveat, and it will bite anyone on a default Ubuntu install. Some distributions use `@` for the rootfs subvolume and `@home` for the home subvolume as a convention, in which case the `subvolume` declarations in the examples have to be changed to match. The README flags this directly rather than leaving you to discover why nothing is found.
Before any of this, the command the README asks for after a configuration change is the dry run, which reads all btrfs information on the source and target filesystems and shows what actions would be performed without writing anything to the disks.
Remote targets, ssh_filter_btrbk.sh, and ondemand snapshots
The complex scenario the README opens with is a server receiving backups from several hosts over ssh with different retention policies per host, which is the case that separates btrbk from a cron script with rsync in it.
Transfer by ssh is listed as a key feature, and OpenSSH is the only real prerequisite for it. The repository ships a shell script named `ssh_filter_btrbk.sh`, and the Makefile installs it along with the other helper scripts into a scripts directory under the installation prefix, which is the conventional placement for something used on the receiving side of an ssh connection. Its name indicates its role: it is the filter that runs when a backup client connects, and the reason to have one at all is that a backup target should be able to accept a btrfs send and nothing else.
The other half of the remote story is the option to create snapshots only when the destination is present. In the laptop example the README shows the line commented out, with the note that it creates snapshots only if the backup disk is attached:
#snapshot_create ondemandThat single option changes the character of a mobile backup. Without it, a cron job on a laptop takes a snapshot every hour whether or not anything is listening, and the snapshots accumulate until the disk is attached and a transfer happens. With it, no snapshot is taken until the backup target is reachable, so the history on the machine matches the history that was actually sent.
There is also a transaction log in the feature list, which is the mechanism that makes interrupted transfers recoverable. A send is a stream, and a stream interrupted by a closed lid is a partial send that the receiving side has to be able to identify and discard. Having a log of what was in flight is what lets btrbk tell a partial send from a complete one rather than guessing from the filesystem state.
The remaining scripts in `contrib` fill in the operational gaps. There is a cron wrapper for mailing results and another for verification, a migration tool for the raw-to-sidecar change, the PBKDF2 helper for encrypted targets, and a raw restore tool. That last one matters for a reason the README does not spell out: an incremental chain is only useful if you can walk it, and a restore tool is what makes the oldest backup reachable.
The Makefile installs to cron.daily, the README example uses cron.hourly
There is a small inconsistency between the build system and the documentation, and it is the kind that produces a machine that silently backs up at the wrong frequency.
The README's local snapshot walkthrough ends with a cron file at `/etc/cron.hourly/btrbk`, containing a shebang and one exec line:
#!/bin/sh
exec /usr/bin/btrbk -q runThe Makefile, on the other hand, defines `CRONDIR` as `/etc/cron.daily`, and `contrib/cron/` is where the shipped cron wrappers live. So the two disagree about the intended cadence.
This matters more than a documentation nit because the retention configuration is expressed in timestamp units. With `snapshot_preserve 48h` and a daily cron job, you keep two snapshots, not forty-eight hours of history at hourly resolution. If you install the packaged version and assume you followed the README, your backup frequency has silently changed by a factor of twenty-four and your retention has changed with it.
The rest of the Makefile is a well-organised install for a project of this size, and reading it tells you what a distribution package would contain. `PREFIX` defaults to `/usr` and is overridable, `CONFDIR` is `/etc`, binaries go to `$(PREFIX)/bin`, documentation to a `share/doc/btrbk` directory, helper scripts to `share/btrbk/scripts`, systemd units to `lib/systemd/system`, bash completions to `share/bash-completion/completions`, and man pages to `man1` and `man5`. The install target is a list of eight sub-targets covering the binary, the binary link, the config, completions, systemd units, shared scripts, man pages and docs.
Two details in there are worth noting for anyone packaging this. The default `all` target is `man`, so a plain `make` only builds documentation and never touches your system, and a comment says there is no need to run it at all if you do not want man pages. And the systemd unit filenames are hardcoded in the install-systemd target for simplicity, with their paths filled in by a sed substitution over `@PN@`, `@CONFDIR@`, `@BINDIR@` and the rest, which is the standard approach for generating unit files at install time.
btrbk against a cron and rsync setup, and against Borg or Restic
Two comparisons, and they are decided by whether your data is on btrfs.
Against the conventional approach, which is a timer in cron calling rsync with hard links, the difference is where the filesystem features come from. rsync with `--link-dest` gives you space-efficient copies and can work on any filesystem, which is why it is the default answer. It cannot give you an atomic point-in-time snapshot, because a directory tree is being read while it is written, so a backup taken from a live directory can capture a file half-written. On btrfs, btrbk takes a real subvolume snapshot first and then sends that, so the backup is consistent by construction rather than by luck. It also gets incremental transfer from the filesystem instead of reimplementing it, and it can send over ssh natively to a btrfs target, with encrypted targets for non-btrfs storage.
The cost of that is the obvious one. btrbk requires btrfs on the source, and while it can write encrypted backups to non-btrfs storage, a machine whose root filesystem is ext4 cannot use it as a general system backup tool. If your data is not on btrfs, this is the wrong tool and no configuration changes that.
Against Borg or Restic, the difference is deduplication scope and the target format. Borg and Restic are content-addressed: they chunk and deduplicate across the entire repository, so a file that appears in several backups is stored once, and they are format-agnostic about the source filesystem. btrbk works at the subvolume snapshot level, so its incremental efficiency comes from btrfs knowing what changed. That is a different trade. btrbk's advantage is that a snapshot is a real filesystem state you can mount and browse, and a restore is a send operation with no reconstruction step. Its disadvantage is that the savings are bounded by what btrfs's copy-on-write gives you, and the retention model is snapshot-count based rather than content based.
For a btrfs system with a handful of subvolumes and a second disk or a second machine, btrbk is the more direct tool. For a mixed estate, or for backups that must go to object storage, the content-addressed tools cover more ground.
No GitHub releases, versions tracked on the project site
The maintenance record needs a note about where to look, because the obvious place is empty.
The repository has no GitHub releases. Versions are referenced in the README, including the note that mounting subvolid=5 became recommended rather than mandatory at version 0.32.0, and the source tarballs are published on the project's own download page under `digint.ch/download/btrbk/releases/`. So the release history lives outside the forge, which is a choice that keeps the repository free of tag sprawl but means `git tag` is not the place to look for a version list.
The repository itself is not archived and the last push was on 2026-07-19. The top-level `ChangeLog` is the in-tree record of what changed, and it is a hand-maintained file rather than generated from commits, which for a single-script project is a reasonable trade.
The documentation is the strongest part of this project and it is worth saying so plainly, because it is unusual. Beyond the README there is a `doc` directory with the installation instructions, the `btrbk(1)` man page for the command line, the `btrbk.conf(5)` man page for the configuration file, and a `btrbk.conf.example` that ships as part of the distribution. Four layers of documentation for a program that is one file, each aimed at a different moment: deciding whether to use it, installing it, running it, and configuring it.
The licence is the GNU General Public License version 3, with the text in the `COPYING` file at the repository root, which is the GNU naming convention rather than a file called `LICENSE`. Redistribution and modification carry the usual conditions. The scripts under `contrib` are part of the same distribution, including the Python key derivation helper, so their terms are the same.
What this project is, in the end, is a mature single-purpose tool that has made its trade-offs explicitly: btrfs required, one file to deploy, a configuration language you learn once, and a retention model you have to understand before you trust it with anything you cannot lose.
Editorial conclusion
Use btrbk when your data already lives on btrfs and you want snapshots that are cheap, atomic and sendable over ssh without maintaining a second copy of your data somewhere else. Do not reach for it to back up a machine that is not btrfs, and read the retention section twice before you trust the numbers, because snapshot_preserve_min and snapshot_preserve mean different things and getting them wrong silently deletes history. Verify first by running btrbk run -n and reading the dry-run output, creating the snapshot directory yourself with mkdir since btrbk will not do it, and checking that subvolid=5 is mounted if you intend to back up the root filesystem.
Frequently asked questions
What is btrbk and what does it require?
Btrbk is a backup tool for btrfs subvolumes that creates atomic snapshots and transfers them incrementally to backup locations. It is a single Perl script needing btrfs-progs at version 4.12 or later, a Perl interpreter, OpenSSH for remote transfers, and mbuffer if you want rate limiting and progress bars.
How do I install btrbk?
The README gives three commands: download the script with wget from the master branch of the repository, make it executable, and run btrbk ls / as a first check. There is a source tarball on the project download page, and the Makefile installs the script, a binary link, config, bash completions, systemd units, helper scripts, man pages and docs under a configurable PREFIX.
How do I check a btrbk configuration before it does anything?
Use the dry-run option, for example btrbk -c /path/to/myconfig -v -n run. The README says this reads all btrfs information on the source and target filesystems and shows what actions would be performed without writing anything to the disks, and it recommends doing this after every configuration change.
What is the difference between snapshot_preserve_min and snapshot_preserve?
snapshot_preserve_min is a minimum age that all snapshots are kept for, whether they were created by cron or by hand; the README's example is 18h. snapshot_preserve is how many snapshots are kept in units of the configured timestamp format, so 48h with an hourly cron job means 48 hourly snapshots.
Does btrbk create the snapshot directory for me?
No. The README states that btrbk does not create subdirectories by default, so the snapshot directory must be created manually, for example with mkdir /mnt/btr_pool/btrbk_snapshots, before the first run.
What licence and documentation does btrbk ship with?
It is licensed under the GNU General Public License version 3, with the text in the COPYING file at the repository root. Documentation includes installation instructions, the btrbk(1) man page, the btrbk.conf(5) man page and a btrbk.conf.example configuration file.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/digint-btrbk)