Overview

A snapshot is a consistent view of a file system at a certain point in time. If you take a snapshot of a file system, and later create a new file or modify an existing one or make any other changes to the file system, the snapshot will still have the old version of the file system without the changes. A snapshot can be mounted as a file system to give a read-only view of a past state of the file system it's a snapshot of.

Unlike copies, snapshots use space on disk only for data that have been overwritten since the snapshot was taken, and can be taken for a whole file system at once without any cooperation from applications to suspend their activity.

Snapshots are often used to take backups. This ensures that files such as sqlite3 databases aren't concurrently modified while the backup is being taken. Without snapshots, such files can be backed up reliably only with application-specific cooperation like sqlite3 backup, or while applications are not running. Caveats:

On some file systems—ffs and zfs—snapshots can be persistent: you can take a snapshot, stored in the file system itself, and it will persist until it is explicitly deleted, even if the system is rebooted. All disk-backed file systems in NetBSD, such as ext2fs and msdosfs, also support external but non-persistent snapshots.

Persistent snapshots in ffs

Persistent snapshots with fss(4) are currently supported for ffs file systems only—not to be confused with the fss file system snapshot device used to manage them.

A persistent snapshot of a file system is represented by a file inside the file system (with a special flag so it can't be read or written normally), and persists until the file is unlinked. This file is used as a backing store for the snapshot, to save any data overwritten later in the file system. Snapshots can be created with or loaded back into fss(4) devices in order to mount them. Active snapshots are managed with the fssconfig(8) command.

File systems may impose a limit on the number of snapshots that can be stored at any time. For ffs, this limit is 20.

Cost of snapshots. In ffs, overwriting a block that was in use in any snapshots causes a copy of the old block content to be saved in each of the snapshots separately, whether or not they are actively configured with fss(4). So while snapshots are more space-efficient than copies when most blocks are unchanged, they can cause write amplification by a factor of up to the number of snapshots.

Taking a snapshot

To create and mount a persistent snapshot of the /home file system, via /dev/fss0:

# fssconfig fss0 /home /home/.snap/20260706.store
# mkdir /home/.snap/20260706
# mount /dev/fss0 /home/.snap/20260706

Users can then browse their home directories in /home/.snap/20260706 as they existed at the time the snapshot was taken. There's nothing special about the directory name .snap; you can store snapshots anywhere inside the file system, and mount them anywhere at all.

Snapshots appear as files with the snap (SF_SNAPSHOT) flag, visible with the ls(1) option “-o”; see chflags(1). The nominal size of a snapshot file is the size of the whole file system, but it only uses up space for the old content of blocks that have been overwritten since it was taken:

# ls -hlo /home/.snap
total 8.0G
-rw-------  1 root  wheel  snap 4.0G Jun 22 02:29 20260622.store
drwxr-xr-x  2 root  wheel  -    512B Jun 22 02:28 20260622
-rw-------  1 root  wheel  snap 4.0G Jul 06 01:18 20260706.store
drwxr-xr-x  2 root  wheel  -    512B Jul 06 01:17 20260706
# du -ahx /home/.snap/
1.6G    /home/.snap/20260622.store
1.7M    /home/.snap/20260706.store
2.0K    /home/.snap/20260622
2.0K    /home/.snap/20260706
1.6G    /home/.snap/

Note: find(1) cannot currently filter for snapshot files; see PR #60380. Also, file(1) does not recognize snapshot files as such; see PR #60379.

The snapshot can be unmounted and unconfigured:

# umount /home/.snap/20260706
# fssconfig -u fss0

But the snapshot will persist at /home/.snap/20260706.store, even after a reboot, and can be configured and mounted again:

# fssconfig fss0 /home /home/.snap/20260706.store
# mount /dev/fss0 /home/.snap/20260706

Recovering from a snapshot

If you made a mistake and need to recover from the snapshot, you can use, e.g., net/rsync to revert files back to what they were in the snapshot:

# rsync -aHc --delete /home/.snap/20260706/bofh/. /home/bofh/.

It is better to use rsync rather than tar(1) for this purpose, in order to avoid writing unchanged content back to the file system, since that would waste space in the snapshot backing store.

Discarding a persistent snapshot

When done with a snapshot, after unmounting and unconfiguring the fss(4), you can unlink the snapshot's backing store file to irrevocably discard it and free up any space it was using in the file system:

# umount /home/.snap/20260706
# fssconfig -u fss0
# rmdir /home/.snap/20260706
# rm /home/.snap/20260706.store

External snapshots with any disk-backed file system

Any file system mounted from a disk device, such as ext2fs and msdosfs, supports external (non-persistent) snapshots with fss(4). This requires a backing store outside the file system, where any existing data in the file system will be saved if later overwritten while the snapshot is active. The backing store can be either a raw character disk device or a regular file in another file system. Active snapshots are managed with the fssconfig(8) command.

fss(4) only supports file systems mounted from disk devices, so it does not support, for example:

Cost of active snapshots. Overwriting a block that was in use in any active snapshots causes a copy of the old block content to be saved in each of the snapshots separately, causing write amplification by a factor of up to the number of snapshots configured.

Taking a snapshot

To create a snapshot of the /boot directory, using space in /var/tmp/ to save any data overwritten while the snapshot is active, and then mount it at /mnt:

# fssconfig fss1 /boot /var/tmp/
# mount /dev/fss1 /mnt

fssconfig(8) will create a temporary file with an unpredictable name in /var/tmp/ as the backing store for the snapshot, and immediately unlink it; it will be discarded when the snapshot is unconfigured.

A snapshot can also use a raw character disk device as the backing store:

# fssconfig fss1 /boot /dev/rld0e

When done, you can unmount the snapshot and unconfigure it:

# umount /mnt
# fssconfig -u fss1

zfs snapshots

In a zfs zpool, any dataset—a mountable file system or a zvol block device—can be snapshotted with the “zfs snapshot” command; see zfs(8) for details. The snapshots for a dataset <pool>/<datasetpath> are named <pool>/<datasetpath>@<snapname>, and can be listed with “zfs list -t snap <pool>/<datasetpath>”. Datasets can be rolled back to a snapshot with “zfs rollback”, or cloned from a snapshot with “zfs clone”. All snapshots in zfs are persistent until deleted with “zfs destroy”.

The snapshots of a file system are automounted on demand as subdirectories of its .zfs/snapshot/ directory, at the root of the file system. The .zfs/ directory is hidden from directory listings by default, but it can be exposed with “zfs set snapdir=visible <pool>/<fs>”.

The snapshots of a zvol are available at /dev/zvol/dsk/<pool>/<zvol>@<snapname> (block device) and /dev/zvol/rdsk/<pool>/<zvol>@<snapname> (raw character device).

Taking a snapshot

To create a snapshot of a dataset, and all datasets under it atomically:

# zfs snapshot -r rpool/home@20260706

The current state of /home will now be visible under /home/.zfs/snapshot/20260706. If you have a separate zfs file system for each user's home directory, the current state of jruser's home directory will be visible under /home/jruser/.zfs/snapshot/20260706 (NOTE: not under /home/.zfs/snapshot/20260706/jruser, which will just appear as an empty directory).

Recovering from a snapshot

You can recover individual files from the .zfs/snapshot/<snapname>/ directory, but you can also roll back a whole file system to the latest snapshot with “zfs rollback”:

# zfs rollback rpool/home/jruser@20260706

Discarding a snapshot

When done with a snapshot, you can destroy it with “zfs destroy”:

# zfs destroy rpool/home/jruser@20260706

You can also recursively destroy all snapshots of the same name on datasets under a path:

# zfs destroy -r rpool/home/jruser@20260706

WARNING: If you enter the command before you finished typing, you might accidentally destroy the file system instead of just a snapshot! zfs(8) uses the same “zfs destroy” command for destroying file systems, zvols, and snapshots.

One technique to avoid this mistake is to start by typing “xxxzfs” instead of “zfs” so if you enter the command too early, it will fail harmlessly:

# xxxzfs destroy -r rpool/home
-sh: xxxzfs: not found
# xxxzfs destroy -r rpool/home@20260706
-sh: xxxzfs: not found

Or start by typing “echo zfs” instead of “zfs”, or use “zfs destroy -n”, especially if you are running a loop over many different snapshot names, to review what you would do.

Then, once you have finished typing the whole command, go back to the beginning of the line and remove the xxx:

# zfs destroy -r rpool/home@20260706

Taking backups with snapshots

A snapshot of a file system won't help you recover if the disk it is mounted from fails. You can use snapshots to take backups, without corrupting live application data in the backups, in various ways:

Many other tools exist to back up data from arbitrary file systems, such as:

These tools can be used on any mounted snapshot.

Unprivileged snapshots and backups

By default, any user in the “operator” group (see groups(7)) can configure fss(4) snapshots, read disk devices, and write to tape devices to take backups—this way backups can be taken without root privileges, at least for some file systems like ffs and lfs.

However, since “dump -X ... /fs” requires creating a snapshot backing store at /fs/<tempfile>, running commands such as “dump -X ... /” or “dump -X ... /usr” unprivileged will fail because / and /usr are writable only by root. Instead, you can create a directory /operator that group “operator” can write to, and use “dump -x /operator/ ... /” to create the snapshot file for / at /operator/<tempfile>.

Example: tar

Mount a persistent snapshot of /home and back it up with tar(1):

# fssconfig fss0 /home /home/.snap/20260706.store
# mkdir /home/.snap/20260706
# mount /dev/fss0 /home/.snap/20260706
# tar -C /home/.snap/20260706 -czf - . \
    >/nfs/backups/home/20260706.tgz

Example: tarsnap

Mount a (single-use) “persistent” snapshot of /var and back it up with tarsnap:

# fssconfig fss3 /var /var/
# mount /dev/fss3 /mnt
# touch /tmp/20260706
# tarsnap -C /mnt -cf var-20260706 --snaptime /tmp/20260706 .
# rm /tmp/20260706
# umount /mnt
# fssconfig -u fss3

Although this uses a “persistent” snapshot, when fssconfig(8) is passed a directory as the backing store (here /var/), it will immediately unlink the backing store when the snapshot is created; later, the snapshot will be discarded as soon as it is unconfigured.

Tarsnap takes incremental backups and caches the mtimes of files each time it runs in order to avoid spending time processing files that haven't changed the last time. The “--snaptime” argument gives a file whose ctime must predate the snapshot; see caveat about mtime below for why this is necessary to avoid data loss in later incremental backups.

Example: borgbackup

Take a zfs snapshot of /usr and back it up with sysutils/py-borgbackup:

# zfs snap -r rpool/usr@20260706
# (cd /usr/.zfs/snapshot/20260706 &&
    borg create borghost:/path/to/repo::usr-20260706 .)

WARNING: If used to take incremental backups, this may be vulnerable to data loss with naive mtime checks; see caveat about mtime below.

Example: rsync

Mount an external snapshot of the msdos volume /data and back it up incrementally with net/rsync:

# fssconfig fss1 /data /var/tmp/
# mount /dev/fss1 /mnt
# ssh rsynchost mkdir /backups/$(hostname)/data/20260706
# rsync -aHc --link-dest=../latest/. \
    /mnt/. rsynchost:/backups/$(hostname)/data/20260706/.
# ssh rsynchost ln -sfn 20260706 /backups/$(hostname)/latest
# umount /mnt
# fssconfig -u fss1

This will use a temporary file in /var/tmp/ as the backing store while the snapshot is active, and then discard it as soon as it is unconfigured.

WARNING: This may be vulnerable to data loss with naive mtime checks; see caveat about mtime below.

Caveat: Incremental backups and mtimes

Some incremental backup tools such as tarsnap, borg, or rsync will record the mtimes of files they have backed up to avoid reprocessing unchanged files the next time around. Although backing up a snapshot rather than the live file system avoids writing corrupted live application data into the backup (at least from applications that can recover from interruption), mtime-based incremental backups may have a subtle race condition that can lead to data loss if you are not careful.

Suppose:

  1. A file is modified at time t + 0.1sec.
  2. A snapshot is taken at time t + 0.2sec and backed up.
  3. The file is modified again at time t + 0.3sec.

In this case, the file's mtime before the snapshot and after the snapshot may both be rounded down—whether by the file system, by the backup tool, or by the backup archive format—to just a number of seconds t since the epoch. The next time the tool runs, it may conclude the file was unchanged since the last run, and lose the second modification. (Of course, in this scenario, trying to back up the live file system instead of a snapshot may lead to the even worse outcome of storing a corrupted file with some garbled mixture of the modifications instead!)

The tarsnap “--snaptime <reference>” option avoids this problem by treating any file with an mtime equal to or newer than the ctime t of <reference> as if it had an mtime of t − 1 in this run when comparing mtimes on the next run. Other backup tools may have—or may not have, but may need!—similar options to reliably take incremental backups.

The dump(8) options “-X” and “-x <snapstore>” also avoid this problem by recording a time just before taking the snapshot as the dump date; on the next incremental run, dump(8) will include any file with an mtime equal to the last dump date or newer.