Building a NAS with ZFS: Complete Guide
Why ZFS for Your Homelab?
ZFS is a powerful filesystem offering:
- Data Integrity: Built-in checksums and RAID
- Snapshots: Point-in-time backups
- Compression: Save storage space automatically
- Deduplication: Eliminate duplicate data
- Copy-on-Write: Safe updates and easy rollback
System Requirements
- Linux system (Ubuntu Server recommended)
- Minimum 2GB RAM (8GB+ recommended)
- Multiple hard drives or SSDs
- ECC RAM recommended for data safety
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.