Getting Started with ZFS Snapshots

Last edited on 2025-06-27 Tagged under  #zfs   #freebsd   #bsd 

I use the snapshot feature of ZFS to track daily changes in my home directory on FreeBSD. This enables me to do things like restore files accidentally deleted and make backups to remote devices.



Introduction

FreeBSD offers excellent integration of the ZFS file system in its base operating system, and during installation I chose to use ZFS and put the entire root filesystem on a single "pool" on my storage device to hold all my "datasets" (auto-created by the FreeBSD installer). The FreeBSD Handbook has a very helpful chapter explaining ZFS and its terminology.

After booting into my new system, I can view the ZFS layout with zfs-list(8) in the format [pool]/[dataset]/[child_dataset].

A sample output:

$ zfs list
NAME                                           USED  AVAIL  REFER  MOUNTPOINT
zroot                                          184G   684G    96K  /zroot
zroot/ROOT                                    9.71G   684G    96K  none
zroot/ROOT/default                            9.71G   684G  8.16G  /
zroot/home                                     173G   684G   104K  /home
zroot/home/dwa                                 171G   684G   170G  /home/dwa
zroot/tmp                                      592K   684G   592K  /tmp
zroot/usr                                     1.66G   684G    96K  /usr
zroot/usr/ports                                837M   684G   837M  /usr/ports
zroot/usr/src                                  861M   684G   861M  /usr/src
zroot/var                                     1.30M   684G    96K  /var
zroot/var/audit                                 96K   684G    96K  /var/audit
zroot/var/crash                                 96K   684G    96K  /var/crash
zroot/var/log                                  804K   684G   804K  /var/log
zroot/var/mail                                 144K   684G   144K  /var/mail
zroot/var/tmp                                   96K   684G    96K  /var/tmp

First Snapshot

From the FreeBSD Handbook:

Snapshots are one of the most powerful features of ZFS. A snapshot provides a read-only, point-in-time copy of the dataset. With Copy-On-Write (COW), ZFS creates snapshots fast by preserving older versions of the data on disk... Snapshots preserve disk space by recording just the differences between the current dataset and a previous version... [and] use no extra space when first created, but consume space as the blocks they reference change.

Sounds good!

EXAMPLE
I want to track changes to data in the home directory of my user - /home/dwa - which is stored in the ZFS dataset zroot/home/dwa. This is the dataset I will use for the examples provided below.

By default, snapshots are created by the root user. I give my user the ability to snapshot my home directory (and only my home directory) by using zfs-allow(8) to grant the snapshot permission:

# zfs allow [user] [permission] [dataset]

which translates as:

# zfs allow dwa snapshot zroot/home/dwa

Snapshots are created using zfs-snapshot(8) in the form:

$ zfs snapshot [dataset]@[snapshot]

The @ symbol is the delimiter between the dataset and the name of the snapshot.

For the first snapshot of my home I use the name @test1, which translates as:

$ zfs snapshot zroot/home/dwa@test1

List snapshots and their properties with zfs-list(8):

$ zfs list -t snapshot
NAME                                       USED  AVAIL  REFER  MOUNTPOINT
zroot/home/dwa@test1                         0B      -   170G  -

The USED size of the snapshot at creation is zero, though it REFERences the 170G of data in the home directory. That size will increase as data is removed or modified in home, while the snapshot preserves its original contents.

As a test, I add/delete/modify a few files inside home, and USED has increased in size:

$ zfs list -t snapshot
NAME                                       USED  AVAIL  REFER  MOUNTPOINT
zroot/home/dwa@test1                      9.88M      -   170G  -

Use zfs-diff(8) to list the differences between the time the zroot/home/dwa@test1 snapshot was taken and the current state of my home directory:

$ zfs diff zroot/home/dwa@test1
Unable to obtain diffs: 
   The sys_mount privilege or diff delegated permission is needed
   to execute the diff ioctl

My user only has the snapshot permission that was assigned earlier for this dataset. Grant my user the diff permission:

zfs allow dwa diff zroot/home/dwa

Now I can list what has been + added, M modified, and - deleted:

$ zfs diff zroot/home/dwa@test1
M /home/dwa/.local/state/nvim/swap
- /home/dwa/tmp/video_list
+ /home/dwa/z13245.mp4

Snapshots are stored in a hidden .zfs/snapshot/ directory inside the dataset. List the contents by directly calling on that directory:

$ ls /home/dwa/.zfs/snapshot/
test1/

I can restore the missing video_list file simply by copying it from the snapshot back to the original directory:

$ cp /home/dwa/.zfs/snapshot/test1/tmp/video_list /home/dwa/tmp

Second Snapshot

Take another snapshot:

$ zfs snapshot zroot/home/dwa@test2

List the snapshots again and their properties:

$ zfs list -t snapshot
NAME                                       USED  AVAIL  REFER  MOUNTPOINT
zroot/home/dwa@test1                      15.0M      -   170G  -
zroot/home/dwa@test2                         0B      -   171G  -

Changes continue to accumulate in home that are being tracked since the creation of @test1. Snapshot @test2 was just created and uses zero space.

Automate Snapshot Creation

Instead of creating snapshots of home manually, I create a script that performs the task:

#!/bin/sh

snap="zroot/home/dwa@$(date +%Y-%m-%d_%H%M%S)"
zfs snapshot $snap

Save as zfs_snapshot_home.sh in ~/bin.

Edit the user's crontab to run the script daily:

$ crontab -e

Create a cronjob that will run the script daily at a designated time (12:15):

#minute	hour	mday	month	wday	command
15 12 * * * /home/dwa/bin/zfs_snapshot_home.sh

Destroy Snapshots

WARNING
When destroying snapshots, make sure there is an @ in the snapshot name portion of the command. Otherwise, you will destroy the underlying file system!

The zfs-destroy(8) command is used to delete snapshots. Again, by default, its a function only root can perform.

Allow my user to destroy snapshots by granting permission for destroy and mount, limited to the zroot/home/dwa dataset:

# zfs allow dwa destroy,mount zroot/home/foo

View what users and permissions have been assigned to them on a dataset:

$ zfs allow [dataset]

which translates in this example as:

$ zfs allow zroot/home/dwa
---- Permissions on zroot/home/dwa ---------------------------------------
Local+Descendent permissions:
	user dwa destroy,diff,mount,snapshot

Destroying a snapshot (example: @test2) is as simple as:

$ zfs destroy zroot/home/dwa@test2

I have quickly come to appreciate how useful snapshots can be. But so far everything has transpired on my local storage, and snapshots are not backups.

Next: How to Use ZFS Snapshots to Make Backups to a Home Server (TODO)

Resources

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

Thanks for reading! Read other posts?

« Previous: FreeBSD: After the First Boot