ZFS Snapshots and Backups Part 2: Use Snapshots to Make Backups to a Home Server

Last edited on 2025-07-04 Tagged under  #zfs   #freebsd   #bsd 

Part 1: Getting Started with Snapshots
Part 2: Use Snapshots to Make Backups to a Home Server

In Part 1 I explained how I've started using the ZFS filesystem on FreeBSD and how quickly I've come to appreciate one of its most powerful features: snapshots.

Snapshots are useful for tracking changes in my home directory, but these snapshots are stored locally on the device. They are not backups of the data. To employ ZFS snapshots as part of my personal backup strategy, these local snapshots need to be copied to a remote device.

I use the commands zfs-send(8) and zfs-receive(8) to make the first full backup from my laptop CLIENT to my home SERVER. Subsequent incremental backups track changes over time as measured against that full backup.

In this HOWTO both my CLIENT and SERVER are running FreeBSD and are using the ZFS filesystem.



Encrypted Dataset

On SERVER use zpool-list(8) to display existing pools:

# zpool list
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
zroot   896G  4.46G   892G        -         -     0%     0%  1.00x    ONLINE  -

In pool zroot I create a new encrypted dataset called backup using zfs-create(8) to hold the snapshots of data copied over from CLIENT.

Encryption in ZFS is per-dataset and applied at creation, and in this instance we want to be prompted to enter a passphrase whenever we mount the dataset:

# zfs create -o encryption=on -o keyformat=passphrase -o keylocation=prompt zroot/backup
Enter new passphrase:
Re-enter new passphrase:

I organize the backups by sub-directories of [hostname_of_CLIENT]/[dataset], so I create a child dataset as:

# zfs create zroot/backup/[hostname_of_client]/[dataset]

In this HOWTO I use the example hostname of oumuamua for my CLIENT, and I will be backing up the home dataset of my user. This translates as:

# zfs create zroot/backup/oumuamua/home

Child datasets inherit the encryption property by default from the parent dataset. Verify with the zfs-get(8) command:

# zfs get -r encryption,keylocation,keyformat zroot/backup
NAME                        PROPERTY     VALUE        SOURCE
zroot/backup                encryption   aes-256-gcm  -
zroot/backup                keylocation  prompt       local
zroot/backup                keyformat    passphrase   -
zroot/backup/oumuamua       encryption   aes-256-gcm  -
zroot/backup/oumuamua       keylocation  none         default
zroot/backup/oumuamua       keyformat    passphrase   -
zroot/backup/oumuamua/home  encryption   aes-256-gcm  -
zroot/backup/oumuamua/home  keylocation  none         default
zroot/backup/oumuamua/home  keyformat    passphrase   -

Locks and Mounts

There are two parts to accessing encrypted datasets: (un)loading encryption keys, and (un)mounting the datasets.

Once I enter the passphrase to load the encryption key, I can zfs-mount(8) and unmount the dataset without being prompted again for the passphrase (key stays in memory):

# zfs umount zroot/backup
# zfs mount zroot/backup

If I both unmount and zfs-unload-key(8), the dataset can't be re-mounted:

# zfs umount zroot/backup
# zfs unload-key -r zroot/backup
1 / 1 key(s) successfully unloaded
# zfs mount zroot/backup
cannot mount 'zroot/backup': encryption key not loaded

Its necessary to load the key first, then (re)mount the encrypted dataset:

# zfs load-key zroot/backup
Enter passphrase for 'zroot/backup':
1 / 1 key(s) successfully loaded
# zfs mount zroot/backup

Testuser

Before I start using ZFS to move large amounts of data for my primary user, I create a new user account for the express purpose of testing backups.

On both my CLIENT and SERVER systems, I use adduser(8) to create testuser. The adduser utility is interactive and walks through the steps for creating a new user account:

# adduser

Accept all the defaults.

SSH Keys

ZFS will use SSH to send data from the CLIENT to SERVER.

I use a combination of SSH keys and keychain(1) to provide passwordless logins between local and remote devices. This will become especially useful in the future when the process of sending and receiving data is scripted and automated.

See my earlier post Configure SSH on FreeBSD for Passwordless Logins to Servers about configuring testuser on both CLIENT and SERVER to use SSH keys for login.

ZFS Allow

By default, many ZFS commands require root privileges to execute.

I want to avoid excessive use of root. Use zfs-allow(8) to delegate to testuser the ability to create, send, receive, and destroy snapshots for home on CLIENT and SERVER.

For testuser on CLIENT

To run zfs send I use this combination:

# zfs allow testuser compression,create,destroy,diff,hold,mount,send,snapshot zroot/home

View permissions:

# zfs allow zroot/home
---- Permissions on zroot/home -----------------------------------
Local+Descendent permissions:
	user testuser compression,create,destroy,diff,hold,mount,send,snapshot

For testuser on SERVER

To run zfs receive the testuser requires more extensive delegation of privileges to manage snapshots on the remote device. I use this combination:

# zfs allow testuser atime,compression,create,destroy,diff,hold,mount,mountpoint,readonly,receive,refreservation,release,rollback,snapshot,userprop zroot/backup/oumuamua/home

First Backup

WARNING
Confirm before sending a snapshot that SERVER has sufficient space to receive a full backup of data from CLIENT.

The first time ZFS is used to send a snapshot from CLIENT to SERVER, it performs a full backup (or replication) of the data.

Switch to the testuser account:

$ su -l testuser
$ whoami; pwd
testuser
/home/testuser

Take a snapshot labelled @test1 of home:

$ zfs snapshot zroot/home/testuser@test1

NOTE
When sending a snapshot of a dataset, you must append a [location] name to the name of the destination. The only restriction to the name is that it must not already exist on the destination.

The command to replicate that snapshot is:

$ zfs send [options] [snapshot] | ssh [server_ip_address] zfs receive [options] [dataset][location]

This translates in the example below as a command that:

  • creates a send data stream with verbose stats (v) of the
  • zroot/home/testuser@test1 snapshot, then
  • pipe that stream to SSH SERVER (address: 192.168.1.11) using
  • passwordless login courtesy of the SSH key pair created earlier, so that
  • SERVER can receive the stream with
  • options -o atime=off -o readonly=on and
  • option -u to ensure that the received filesystem remains unmounted, and
  • save the stream to zroot/backup/oumuamua/home/testuser
$ zfs send -v zroot/home/testuser@test1 | ssh 192.168.2.10 zfs receive -o atime=off -o readonly=on -u zroot/backup/oumuamua/home/testuser

NOTE
See https://unix.stackexchange.com/q/467001 for the reasons behind options atime=off and readonly=on.

SSH into SERVER and confirm that the snapshot was created:

$ zfs list -t snapshot
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
zroot/backup/oumuamua/home/testuser@test1     0B      -   500K  -

Incremental Backups

Once the initial replication is complete, you can test sending incremental snapshots.

Copy a few files into /home/testuser and take another snapshot:

$ zfs snapshot zroot/home/testuser@test2

To replicate the differences between the first and second snapshots on the receiving SERVER, add the increment switch (-I) and specify the name of the two snapshots.

This command requires that the first mentioned snapshot to already exist on SERVER (which is true for our example):

$ zfs send -v -I zroot/home/testuser@test1 zroot/home/testuser@test2 | ssh 192.168.2.10 zfs receive -o atime=off -o readonly=on -u zroot/backup/oumuamua/home/testuser

Note that -I would also include any snapshots taken between the two specified snapshots. If @test2 was the last snapshot previously sent to SERVER, and I subsequently took snapshots @test3, @test4, @test5, and @test6, a single command is able to transfer all the new snapshots:

$ zfs send -v -I zroot/home/testuser@test2 zroot/home/testuser@test6 | ssh 192.168.2.10 zfs receive -o atime=off -o readonly=on -u zroot/backup/oumuamua/home/testuser

Next: Automate

Once satisfied with the process of taking snapshots on the CLIENT and sending to the SERVER for backup purposes using testuser, I implement the above configuration for my primary user.

After configuring that to my satisfaction, the next step is twofold: first, create a schedule of snapshots/backups that retains a certain number of snapshots; second, automate the whole process of creating/destroying and sending/receiving snapshots between CLIENT and SERVER.

Next: Automate Snapshots and Backups (TODO)

Resources

You can like, share, or comment on this post on the Fediverse 💬

Thanks for reading! Read other posts?

« Previous: ZFS Snapshots and Backups Part 1: Getting Started with Snapshots