How to Set Up ZFS on Ubuntu Server (A Mirrored Pool with Snapshots and Self-Healing)

ZFS: a mirror that heals itself — Checksums, snapshots, compression. For the data that matters.

A normal filesystem trusts the disk. ZFS doesn't: it checksums every block, and in a mirror it notices when one drive returns bad data and fixes it from the other, silently. It also does snapshots in an instant, compression for free, and sends whole filesystems to another machine for backup. For the drive that holds your photos, documents and Docker data, this is the storage you want — and on Ubuntu it's one package.

Quick reference

TaskCommand
Installsudo apt install zfsutils-linux -y
Make a mirrorsudo zpool create tank mirror /dev/disk/by-id/A /dev/disk/by-id/B
Datasetssudo zfs create tank/photos
Compressionsudo zfs set compression=lz4 tank
Snapshot / roll backsudo zfs snapshot tank/photos@before · sudo zfs rollback tank/photos@before
Healthzpool status · sudo zpool scrub tank
Replace a disksudo zpool replace tank OLD NEW

1. Install and identify the drives

sudo apt install zfsutils-linux -y
ls -l /dev/disk/by-id/ | grep -v part

Always use the /dev/disk/by-id/ names (ata-WDC_WD40EFRX-...), never sdb — those change between boots. Two identical-ish drives for a mirror; the pool's size is the smaller one.

2. Create a mirrored pool

This erases both drives.

sudo zpool create -o ashift=12 -O compression=lz4 -O atime=off -O xattr=sa -m /mnt/tank tank \
mirror /dev/disk/by-id/ata-DRIVE_A /dev/disk/by-id/ata-DRIVE_B
zpool status

One drive is fine to start (zpool create tank /dev/disk/by-id/...) and you can zpool attach a second later to make it a mirror.

3. Datasets instead of folders

sudo zfs create tank/photos
sudo zfs create tank/docker
sudo zfs create -o recordsize=1M tank/media
sudo zfs create -o compression=off tank/backups
sudo chown -R john:john /mnt/tank
zfs list

Each dataset is a filesystem with its own settings, snapshots and quota — recordsize=1M for big video files, compression off for already-compressed backups, sudo zfs set quota=500G tank/photos to cap one.

4. Snapshots

sudo zfs snapshot tank/photos@2026-11-05
zfs list -t snapshot

Instant and free until files change. Browse a snapshot's contents read-only at /mnt/tank/photos/.zfs/snapshot/2026-11-05/. Copy one file back from there, or roll the whole dataset back:

sudo zfs rollback tank/photos@2026-11-05

Automate it: sudo apt install zfs-auto-snapshot keeps hourly, daily, weekly and monthly snapshots and prunes them. Snapshots are not backups (same disks), but they're the fastest undo there is — including against ransomware on a Samba share.

5. Scrub monthly

A scrub reads every block and repairs anything the checksums catch:

sudo zpool scrub tank
zpool status

Ubuntu installs a timer that scrubs on the second Sunday of each month (/etc/cron.d/zfsutils-linux). zpool status showing errors: No known data errors is the sentence you want.

6. When a drive fails

zpool status shows DEGRADED and names the faulted disk. The pool keeps working. Swap the drive, then:

sudo zpool replace tank /dev/disk/by-id/ata-OLD /dev/disk/by-id/ata-NEW
zpool status # watch "resilver" progress

Nothing was lost; nothing was unmounted. That's the point of the mirror.

7. Send it elsewhere

sudo zfs send tank/photos@2026-11-05 | ssh john@pi "sudo zfs receive backup/photos"

Later, zfs send -i sends only the changes since the last snapshot. A second ZFS box (a Pi with a USB drive will do) becomes a block-exact replica.

Memory

The "ZFS needs 1 GB per TB" rule is for deduplication, which you won't turn on. 8 GB of RAM is plenty for a home server with a few terabytes; ZFS uses spare RAM as cache and gives it back.

Troubleshooting

Pool not there after a reboot. sudo zpool import tank; if that's needed every time, sudo systemctl enable zfs-import-cache zfs-mount.

"cannot create: one or more devices is currently unavailable" / has a partition. Old partition table. sudo wipefs -a /dev/disk/by-id/... first.

Docker on ZFS complains about overlay2. Point Docker's data root at a ZFS dataset and let it use the zfs storage driver, or leave /var/lib/docker on the system ext4 disk and put only volumes on the pool.

Slow writes with lots of tiny files. recordsize too large for that dataset; default 128K is right for mixed files.

Wrapping up

One package, two drives by-id in a mirror, a dataset per kind of data, auto-snapshots, a monthly scrub. Silent corruption gets caught, a dead drive is a resilver not a restore, and "I deleted the wrong folder" is a thirty-second rollback.

Call us