Self-hosted service
oetiker/znapzend avatar
oetiker/znapzend

ZnapZend keeps ZFS backup schedules inside the filesystem itself

zfs backup with remote capabilities and mbuffer integration.

672 stars140 forksPerlGPL-3.0

At a glance

What is it?
ZnapZend is a GPL licensed, ZFS centric backup tool that leans on the native snapshot, send and receive commands and stores its retention policy as ZFS properties on each dataset. That design removes a config file but makes recursion semantics, pruning of excluded subtrees and the fate of leftover snapshots the parts you have to understand before trusting it.
Who is it for?
Administrators who already run ZFS and want backup policy to travel with the pool rather than live in a file on one host will find ZnapZend's property-driven model a good fit, especially where a recursive schedule over a dataset subtree is the goal. It is a poor fit if you need per-dataset pruning of an excluded subtree today, since the documentation calls that experimental, or if you cannot accept snapshots of disabled datasets lingering after an unclean shutdown.
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 108 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 October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

ZnapZend delegates the copying to ZFS itself

ZnapZend describes itself as a ZFS centric backup tool for creating snapshots and sending them to backup locations, and the important architectural point is what it does not do. It does not implement its own block transfer. It relies on the ZFS tools `snapshot`, `send` and `receive` to do the work. What ZnapZend adds on top is scheduling and lifecycle management: the built-in ability to manage both local snapshots and remote copies by thinning them out as time progresses. In practice that means it is the component that decides when a snapshot exists, when it is sent to a backup location, and when it is pruned, while the actual data movement is done by the ZFS commands the operating system already ships. The project description also names mbuffer integration, which the container image reinforces by pulling mbuffer into the runtime layer for use in the send pipeline.

The backup schedule is stored as ZFS properties

There is no configuration file to copy around. The configuration is stored as properties in the ZFS filesystem itself, which means a policy attached to a dataset travels with that dataset and is visible with ordinary ZFS tooling. The documentation is careful about what that means, and the caveat is worth internalising: it concerns local ZFS properties of each configured dataset, explicitly not inherited properties and not received properties. There is also domain-specific handling of recursion for certain settings, keyed on the presence and value of an `org.znapzend:recursive` property. So the shape of the configuration is a set of properties on datasets rather than a stanza in a file, and how they inherit or do not inherit follows ZFS property semantics with one project-specific addition layered on top. Anyone migrating an existing setup will find the policy attached to the data rather than to a host, which is convenient for pools that move between machines.

Excluding a subtree with recursive=off instead of many enabled=off lines

Exclusion has been possible for years and has just been generalised. The older mechanism is a local ZFS property `org.znapzend:enabled=off` set in a dataset that descends from one carrying a full backup retention schedule. Such a parent sets `org.znapzend:recursive=off` for its descendants, and exactly those datasets with `enabled=off` are then not tracked with a long-term history locally or remotely. Current releases add the ability to set `org.znapzend:recursive=on` in the excluded datasets themselves, so a whole subtree can be excluded from retention handling with one configuration in the common ancestor, where before each excluded dataset needed its own `enabled=off` line. The documentation gives the CI host as the motivating case: back up `rpool/home` but exclude the location holding discardable bulk data such as build roots or caches in a worker's home. Re-enabling is also technically possible in certain sub-datasets of a disabled tree, though the documentation notes that this path has seen little use and little feedback in real situations.

Snapshots of disabled datasets can linger after an unclean shutdown

This is the sharpest edge in the documentation and it deserves to be understood before the tool touches a production pool. Because snapshots of a dataset with a full retention schedule are made recursively, and because snapshots for not-enabled datasets are pruned afterwards, the creation and deletion of those snapshots are separated in time. The documentation states the consequence plainly: if the host reboots, or the ZnapZend process is interrupted in some other way, at the wrong moment, such snapshots may linger indefinitely and unexpectedly consume disk space for the blocks they uniquely reference. The same note records that different ZnapZend versions have varied in whether they sent those snapshots to a remote destination as part of a recursive send stream and pruned them there afterwards, or avoided such sending. So there is a temporary storage and traffic cost associated with snapshots of not-enabled datasets, and the cleanup is not guaranteed to be prompt.

Building from source needs perl plus an autotools stack

If your distribution does not package ZnapZend, or you want a custom build, you need a compiler and you need to build some prerequisite perl modules into binary libraries for the target operating system and architecture. At run time you need just perl. The Git checkout includes a pregenerated `configure` script, and a rebuild from scratch additionally wants `./bootstrap.sh` and the autoconf and automake stack. On Debian and Ubuntu the documentation gives:

bash
apt-get install perl unzip

and, to bootstrap on those platforms as well:

bash
apt-get install autoconf carton

On RedHat the equivalent is `yum install perl-core`, and the documentation notes that a GNU Make implementation used to be required, which is no longer strictly the case, with Sun Make and BSD Make also known to work, while still suggesting `gmake` as a fallback if system-provided tools fail. On macOS the Xcode command line tools are the prerequisite, and a non-default perl interpreter can be selected by setting the `PERL` environment variable before running `configure`.

The container image carries mbuffer, zfs and perl on Alpine

The repository ships a Dockerfile with a builder stage, a runtime stage and a test stage. The builder compiles and installs the project into `/opt/znapzend` on a Debian-based perl image, and the runtime stage is Alpine, where mbuffer is added from the edge community repository before the ZFS and perl packages:

dockerfile
FROM docker.io/library/alpine:${ALPINE_VERSION} AS runtime
RUN \
  apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/ mbuffer && \
  apk add --no-cache zfs curl bash autoconf automake nano perl openssh && \
  ln -s /dev/stdout /var/log/syslog && \
  ln -s /usr/bin/perl /usr/local/bin/perl

The comments in that file explain the choices: mbuffer is not in Alpine main and community keys expire, so the Alpine base version has to be bumped regularly and the package is requested first to fail fast if it disappears, while perl is deliberately left unpinned because Alpine patch-bumps it within a stable branch. The image ends with `CMD [ "znapzend --logto=/dev/stdout" ]`, so logs go to stdout for a container platform to collect.

Pruning excluded subtrees is still called experimental

Recursive configurations are described as well supported for setting up backup and retention policies over a whole dataset subtree, but pruning of such trees, where you want every dataset under a parent except one, is called experimental: it works, but there may be rough edges requiring more development. The documentation also warns that you probably do not want to enable ZnapZend against the root datasets of your pools, and that you would have to be more fine-grained in your setup. The reason given is the usage of recursive ZFS snapshots, where the command targets one dataset and impacts it and all its children, which allows a consistent point-in-time set of snapshots across multiple datasets. That consistency is the feature that makes recursive schedules attractive for a database host, and it is the same mechanism that makes a misconfigured root dataset or an excluded subtree expensive.

A GPL project with a release history that has recently restarted

ZnapZend is licensed under GPL-3.0, which for a backup tool that runs on your own hosts is mostly a non-issue, but it does mean any modification you distribute travels with the same terms. The project is not archived and the last push to the repository was on 2026-07-07. The release history shows a gap followed by recent work: v0.23.2 on 2024-06-27, then v0.23.4 on 2026-07-04 and v0.23.6 on 2026-07-06, the last one a couple of days before that push. Two releases inside three days at the end of a long quiet period suggests a burst of work rather than a steady stream, and the CHANGES and CHANGES.old files in the repository root are where you would look for what actually changed. The homepage is www.znapzend.org, and the documentation for configuration lives in the `doc/` directory and the manual pages under `man/`.

Editorial conclusion

Administrators who already run ZFS and want backup policy to travel with the pool rather than live in a file on one host will find ZnapZend's property-driven model a good fit, especially where a recursive schedule over a dataset subtree is the goal. It is a poor fit if you need per-dataset pruning of an excluded subtree today, since the documentation calls that experimental, or if you cannot accept snapshots of disabled datasets lingering after an unclean shutdown. Before trusting it on a production pool, compare the datasets ZnapZend reports against the ones you expected to be excluded, and read the property names in the README before setting `org.znapzend:enabled=off` on anything.

Frequently asked questions

Where does ZnapZend keep its configuration?

The configuration is stored as properties in the ZFS filesystem itself rather than in a separate file. It concerns local ZFS properties of each configured dataset, not inherited properties and not received properties, and recursion for certain settings is handled through an `org.znapzend:recursive` property.

How do I exclude a subtree of datasets from ZnapZend backups?

Set the local property `org.znapzend:enabled=off` in the datasets to exclude, which are then not tracked with a long-term history locally or remotely. Current releases also let you set `org.znapzend:recursive=on` in those datasets so a whole subtree is excluded with one configuration in the common ancestor, instead of one `enabled=off` line per dataset.

Is pruning of excluded subtrees reliable in ZnapZend?

The documentation calls pruning of such trees experimental: it works, but there may be rough edges requiring more development. It also warns against enabling ZnapZend against the root datasets of your pools because recursive ZFS snapshots affect the dataset and all its children.

Why can disk space disappear after a ZnapZend host reboots?

Snapshots of not-enabled datasets are created as part of a recursive snapshot of the parent and pruned afterwards, so their creation and deletion are separated in time. The documentation warns that if the host reboots or the ZnapZend process is interrupted at the wrong moment, such snapshots may linger indefinitely and consume disk space for the blocks they uniquely reference.

What does mbuffer do in the ZnapZend Docker image?

The project description names mbuffer integration, and the Dockerfile adds the mbuffer package to the runtime image from the Alpine edge community repository before the ZFS and perl packages. The file also notes that mbuffer is not in Alpine main and that community keys expire, which is why the base version has to be bumped regularly.

Official sources

  1. Issues
  2. License: GPL-3.0
  3. oetiker/znapzend on GitHub
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/oetiker-znapzend.svg)](https://hysenlabs.com/projects/oetiker-znapzend)

Was this useful?