Building a NAS with ZFS: Complete Guide

Why ZFS for Your Homelab?

ZFS is a powerful filesystem offering:

System Requirements

Installation

Install ZFS on Ubuntu

sudo apt-get update
sudo apt-get install -y zfsutils-linux zfs-initramfs

Check ZFS Installation

zfs --version
zpool --version

Drive Preparation

List Available Drives

lsblk
fdisk -l

Identify Drives

For this example, we’ll use /dev/sdb, /dev/sdc, /dev/sdd:

# Check for existing partitions
sudo fdisk -l /dev/sdb

Creating a ZFS Pool

RAID Configurations

# RAID-0 (Striped) - No redundancy, best performance
zpool create tank /dev/sdb /dev/sdc /dev/sdd

# RAID-1 (Mirror) - Full redundancy, half capacity
zpool create tank mirror /dev/sdb /dev/sdc

# RAID-Z1 (3-way redundancy) - Recommended
zpool create tank raidz1 /dev/sdb /dev/sdc /dev/sdd /dev/sde

# RAID-Z2 (2-disk failure tolerance)
zpool create tank raidz2 /dev/sdb /dev/sdc /dev/sdd /dev/sde /dev/sdf

# RAID-Z3 (3-disk failure tolerance)
zpool create tank raidz3 /dev/sdb /dev/sdc /dev/sdd /dev/sde /dev/sdf /dev/sdg

For our example, we’ll use RAID-Z1:

sudo zpool create tank raidz1 /dev/sdb /dev/sdc /dev/sdd

Verify Pool Creation

zpool status
zfs list

Creating Datasets

Datasets are like partitions within your pool:

# Create dataset for media
sudo zfs create tank/media

# Create dataset for backups
sudo zfs create tank/backups

# Create dataset for application data
sudo zfs create tank/appdata

# Set mount points
sudo zfs set mountpoint=/mnt/media tank/media
sudo zfs set mountpoint=/mnt/backups tank/backups
sudo zfs set mountpoint=/mnt/appdata tank/appdata

List Datasets

zfs list

ZFS Properties Configuration

Enable Compression

sudo zfs set compression=lz4 tank/media
sudo zfs set compression=lz4 tank/backups
sudo zfs set compression=zstd tank/appdata

Set Quota

# Limit media dataset to 500GB
sudo zfs set quota=500G tank/media

# Set reservation (guarantee minimum space)
sudo zfs set reservation=100G tank/backups

Enable Deduplication (Use Carefully!)

# Uses significant RAM - enable only if needed
sudo zfs set dedup=on tank/appdata

Set Record Size

# For media files (larger records)
sudo zfs set recordsize=1M tank/media

# For databases (smaller records)
sudo zfs set recordsize=4K tank/appdata

Snapshots and Backups

Create Snapshots

# Manual snapshot
sudo zfs snapshot tank/media@backup-2026-03-10

# Snapshot entire pool
sudo zfs snapshot -r tank@full-backup-2026-03-10

List Snapshots

zfs list -t snapshot

Automated Snapshots with zfs-auto-snapshot

sudo apt-get install zfs-auto-snapshot

# Create snapshots every 4 hours
sudo zfs set com.sun:auto-snapshot:frequent=true tank/media

# Keep snapshots for 7 days
sudo zfs set com.sun:auto-snapshot:daily=true tank/media

Restore from Snapshot

# Rollback to snapshot
sudo zfs rollback tank/media@backup-2026-03-10

# Clone snapshot to new dataset
sudo zfs clone tank/media@backup-2026-03-10 tank/media-restored

Remote Backups

Send Snapshots to Remote System

# Initial full backup
sudo zfs send tank/media@backup-2026-03-10 | \
  ssh user@remote "zfs receive backup/media"

# Incremental backup
sudo zfs send -i tank/media@backup-2026-03-10 \
  tank/media@backup-2026-03-11 | \
  ssh user@remote "zfs receive backup/media"

Automated Backup Script

#!/bin/bash

POOL="tank"
DATASET="media"
REMOTE_HOST="backup.server.local"
REMOTE_USER="backup"
REMOTE_POOL="backups"

# Create snapshot
SNAPSHOT="$DATASET@$(date +%Y%m%d_%H%M%S)"
zfs snapshot "$POOL/$SNAPSHOT"

# Send to remote
zfs send "$POOL/$SNAPSHOT" | \
  ssh "$REMOTE_USER@$REMOTE_HOST" \
  "zfs receive $REMOTE_POOL/$DATASET"

# Cleanup old snapshots
zfs list -H -o name -t snapshot | \
  while read snap; do
    if [[ $snap == *"$DATASET"* ]]; then
      AGE=$(($(date +%s) - $(stat -c %Y /mnt/$snap 2>/dev/null || echo $(date +%s))))
      if [[ $AGE -gt 2592000 ]]; then  # 30 days
        zfs destroy "$snap"
      fi
    fi
  done

Monitoring ZFS

Check Pool Health

zpool status
zpool status -v

Monitor Performance

# Real-time I/O stats
zpool iostat -v 1

# Show operation progress
zpool scrub tank  # Start scrub
zpool status      # View progress

Set Up Alerts

# Check for degraded pools
#!/bin/bash
STATUS=$(zpool status | grep -i "degraded\|faulted")
if [[ ! -z "$STATUS" ]]; then
  echo "ZFS Pool Issue: $STATUS" | \
    mail -s "ZFS Alert" [email protected]
fi

# Add to cron: 0 */4 * * * /usr/local/bin/zfs-check.sh

Regular Maintenance

Monthly Scrub

# Manual scrub
sudo zpool scrub tank

# Automated monthly scrub
# Add to crontab:
# 0 2 1 * * zpool scrub tank

# Monitor progress
zpool status

Update ZFS

sudo apt-get update && apt-get upgrade
sudo modprobe -r zfs
sudo modprobe zfs

Troubleshooting

Device Replacement

# Mark device as faulted
sudo zpool offline tank /dev/sdb

# Replace physical drive
# Then:
sudo zpool replace tank /dev/sdb /dev/sdb1

# Check resilver progress
zpool status -v

Recovery Mode

# Force import pool
sudo zpool import -f tank

# Check for errors
sudo zfs scrub tank

Performance Tuning

ARC Cache Size

# Set maximum ARC to 8GB
echo 8589934592 | sudo tee /sys/module/zfs/parameters/zfs_arc_max

# Make permanent
echo "options zfs zfs_arc_max=8589934592" | \
  sudo tee -a /etc/modprobe.d/zfs.conf

I/O Tuning

# Increase recordsize for large files
zfs set recordsize=1M tank/media

# Adjust sync level
zfs set sync=standard tank/appdata

Conclusion

ZFS provides enterprise-grade data protection for your homelab. Start with a simple RAID-Z1 pool and expand with snapshots and automated backups.

Resources