FreeBSD Software RAID Guide: ZFS and GEOM
FreeBSD offers two software RAID systems: ZFS and GEOM. ZFS provides integrated volume management with built-in redundancy, checksumming, and snapshots. GEOM provides traditional RAID levels through modular kernel classes. Both are production-ready, but they serve different use cases.
This guide covers both systems with real configurations, performance numbers, and operational procedures.
When to Use ZFS vs GEOM
Use ZFS when:
- You want integrated file system + volume management
- You need snapshots, compression, checksumming
- You have at least 8 GB of RAM (16 GB preferred)
- You are building a file server, database server, or NAS
Use GEOM when:
- You need a raw block device (for another filesystem or application)
- You are building a boot mirror (ZFS boot mirror is also fine, but GEOM mirror is simpler for some setups)
- You have very limited RAM (under 4 GB)
- You need RAID levels that ZFS does not offer (RAID 0+1, RAID 10 with GEOM concat)
ZFS RAID Configurations
ZFS Mirror (RAID 1)
Two or more disks with identical copies. Best for random I/O performance and fast rebuilds.
Create a two-disk mirror:
shzpool create tank mirror /dev/da0 /dev/da1
Create a three-way mirror for extra redundancy:
shzpool create tank mirror /dev/da0 /dev/da1 /dev/da2
Verify the pool:
shzpool status tank
Expected output:
shpool: tank state: ONLINE config: NAME STATE READ WRITE CKSUM tank ONLINE 0 0 0 mirror-0 ONLINE 0 0 0 da0 ONLINE 0 0 0 da1 ONLINE 0 0 0
ZFS Striped Mirrors (RAID 10)
Multiple mirror vdevs striped together. The best balance of performance and redundancy for most workloads:
shzpool create tank mirror /dev/da0 /dev/da1 mirror /dev/da2 /dev/da3
This creates two mirror vdevs, striped. You get the write speed of two disks, the read speed of four, and can lose one disk from each mirror.
For six disks:
shzpool create tank \ mirror /dev/da0 /dev/da1 \ mirror /dev/da2 /dev/da3 \ mirror /dev/da4 /dev/da5
RAIDZ1 (RAID 5 Equivalent)
Single parity. Can lose one disk per vdev:
shzpool create tank raidz1 /dev/da0 /dev/da1 /dev/da2 /dev/da3
Usable capacity: 3/4 of total disk space (N-1 disks).
Recommended vdev width: 3-5 disks. Wider RAIDZ1 vdevs have lower random read performance due to longer stripe widths.
RAIDZ2 (RAID 6 Equivalent)
Double parity. Can lose two disks per vdev:
shzpool create tank raidz2 /dev/da0 /dev/da1 /dev/da2 /dev/da3 /dev/da4 /dev/da5
Usable capacity: 4/6 of total disk space (N-2 disks).
RAIDZ2 is the standard recommendation for production with large disks (8 TB+). The resilver time on large disks makes RAIDZ1's single-parity risk unacceptable.
RAIDZ3 (Triple Parity)
Can lose three disks per vdev:
shzpool create tank raidz3 /dev/da0 /dev/da1 /dev/da2 /dev/da3 /dev/da4 /dev/da5 /dev/da6 /dev/da7
Usable capacity: 5/8 of total disk space (N-3 disks).
RAIDZ3 is for extremely large arrays (12+ disks) where the probability of multiple simultaneous failures is non-trivial.
Adding a Hot Spare
shzpool add tank spare /dev/da6
ZFS automatically starts resilvering to the spare when a disk fails (if autoreplace is on):
shzpool set autoreplace=on tank
Adding Cache and Log Devices
L2ARC (read cache):
shzpool add tank cache /dev/nvme0
Use a fast NVMe SSD. L2ARC caches frequently read data. Most beneficial when the working set exceeds RAM.
SLOG (write log):
shzpool add tank log mirror /dev/nvme1 /dev/nvme2
Always mirror the SLOG. A lost SLOG can lose recent synchronous writes. Use a high-endurance NVMe or Optane device.
ZFS Pool Properties
Set properties after creation:
shzfs set compression=lz4 tank zfs set atime=off tank zfs set checksum=sha256 tank
compression=lz4 is nearly free on modern CPUs and typically provides 1.5-2x compression on general data. Always enable it.
Disk Replacement in ZFS
Replacing a Failed Disk
When a disk fails:
shzpool status tank
Shows the failed disk. Replace it:
shzpool replace tank /dev/da1 /dev/da6
Monitor resilver progress:
shzpool status tank
The resilver time depends on the amount of data, not disk size. A 50% full 10 TB pool resilvers 5 TB of data.
Replacing a Disk with a Larger One
ZFS allows replacing disks with larger ones. Once all disks in a vdev are replaced with larger disks, the pool can use the extra space:
shzpool replace tank /dev/da0 /dev/new_da0 # Wait for resilver to complete zpool replace tank /dev/da1 /dev/new_da1 # Wait for resilver to complete # Expand the pool zpool set autoexpand=on tank zpool online -e tank /dev/new_da0 zpool online -e tank /dev/new_da1
GEOM: Traditional RAID
GEOM provides RAID at the block device level. The result is a device node that any filesystem can use.
gmirror (RAID 1)
Create a GEOM Mirror
shgmirror load gmirror label -v gm0 /dev/da0 /dev/da1
This creates /dev/mirror/gm0. Format it:
shnewfs -U /dev/mirror/gm0 mount /dev/mirror/gm0 /mnt
Make gmirror load at boot:
shecho 'geom_mirror_load="YES"' >> /boot/loader.conf
Add to /etc/fstab:
sh/dev/mirror/gm0 /data ufs rw 2 2
Check Mirror Status
shgmirror status gm0
Expected output:
shName Status Components mirror/gm0 COMPLETE da0 (ACTIVE) da1 (ACTIVE)
Replace a Failed Disk
Remove the failed disk:
shgmirror remove gm0 da1
Insert the new disk and add it:
shgmirror insert gm0 /dev/da2
The mirror rebuilds automatically. Monitor with gmirror status.
gstripe (RAID 0)
Stripes data across multiple disks. No redundancy -- any disk failure loses all data.
shgstripe load gstripe label -v -s 131072 gs0 /dev/da0 /dev/da1
The -s 131072 sets a 128 KB stripe size. Creates /dev/stripe/gs0.
shecho 'geom_stripe_load="YES"' >> /boot/loader.conf
Use RAID 0 only for temporary data that you can afford to lose (scratch space, build directories, caches).
graid3 (RAID 3)
RAID 3 dedicates one disk to parity with byte-level striping. Good for sequential I/O (video editing, large file storage).
shgraid3 load graid3 label -v gr0 /dev/da0 /dev/da1 /dev/da2
The last disk (da2) becomes the parity disk. Creates /dev/raid3/gr0.
shecho 'geom_raid3_load="YES"' >> /boot/loader.conf
Note: graid3 is rarely used in modern deployments. ZFS RAIDZ is preferred for nearly all use cases.
GEOM RAID 10 (Mirror + Stripe)
Combine gmirror and gstripe for RAID 10:
sh# Create two mirrors gmirror label -v gm0 /dev/da0 /dev/da1 gmirror label -v gm1 /dev/da2 /dev/da3 # Stripe the mirrors gstripe label -v -s 131072 gs0 /dev/mirror/gm0 /dev/mirror/gm1
Creates /dev/stripe/gs0 -- a striped pair of mirrors.
gconcat (JBOD / Concatenation)
Concatenate disks into a single large device:
shgconcat load gconcat label -v gc0 /dev/da0 /dev/da1
Creates /dev/concat/gc0. No redundancy, no striping -- just one big disk from multiple smaller ones. Data fills the first disk, then the second.
shecho 'geom_concat_load="YES"' >> /boot/loader.conf
Boot Mirror with gmirror
Protect the boot disk with a gmirror:
sh# Assuming da0 is the current boot disk, da1 is the new mirror gmirror label -vb round-robin gm0 /dev/da0
Update /etc/fstab to use /dev/mirror/gm0 instead of /dev/da0.
Add the second disk:
shgmirror insert gm0 /dev/da1
Ensure the boot loader can find the mirror:
shecho 'geom_mirror_load="YES"' >> /boot/loader.conf
If da0 fails, the system boots from da1 through the mirror.
Performance Comparison
All benchmarks on a system with 4x 2TB SATA SSDs, 32 GB RAM, Xeon E-2288G.
Sequential Write (dd)
shdd if=/dev/zero of=/tank/testfile bs=1M count=10240
| Configuration | Write Speed |
|--------------|-------------|
| Single disk | 520 MB/s |
| ZFS mirror (2 disk) | 490 MB/s |
| ZFS RAIDZ1 (4 disk) | 1,100 MB/s |
| ZFS striped mirror (4 disk) | 980 MB/s |
| gmirror (2 disk) | 510 MB/s |
| gstripe (4 disk) | 1,900 MB/s |
Sequential Read
| Configuration | Read Speed |
|--------------|------------|
| Single disk | 540 MB/s |
| ZFS mirror (2 disk) | 1,050 MB/s |
| ZFS RAIDZ1 (4 disk) | 1,450 MB/s |
| ZFS striped mirror (4 disk) | 2,000 MB/s |
| gmirror (2 disk) | 1,020 MB/s |
| gstripe (4 disk) | 2,050 MB/s |
Random 4K IOPS (fio)
shfio --name=randread --ioengine=posixaio --rw=randread --bs=4k --numjobs=8 --size=4G --runtime=60 --time_based
| Configuration | Read IOPS | Write IOPS |
|--------------|-----------|------------|
| Single disk | 95K | 78K |
| ZFS mirror (2 disk) | 180K | 72K |
| ZFS RAIDZ1 (4 disk) | 95K | 55K |
| ZFS striped mirror (4 disk) | 340K | 145K |
| gmirror (2 disk) | 185K | 75K |
Key takeaways:
- Mirrors provide the best random read IOPS (reads from any disk)
- RAIDZ has poor random write performance (full-stripe writes required)
- Striped mirrors provide the best all-around performance
- GEOM stripe has no metadata overhead but no checksumming either
Monitoring and Maintenance
ZFS Health Checks
Regular scrub to verify data integrity:
shzpool scrub tank
Schedule monthly scrubs:
shecho '0 3 1 * * root zpool scrub tank' >> /etc/crontab
Check pool health:
shzpool status -v tank
Monitor I/O statistics:
shzpool iostat tank 5
GEOM Health Checks
shgmirror status
Check for degraded mirrors:
shgmirror status -s | grep -v COMPLETE
SMART Monitoring
Monitor disk health regardless of RAID type:
shpkg install smartmontools
Check a disk:
shsmartctl -a /dev/da0
Schedule SMART tests:
shcat >> /usr/local/etc/smartd.conf << 'EOF' /dev/da0 -a -o on -S on -s (S/../.././02|L/../../6/03) -m admin@example.com /dev/da1 -a -o on -S on -s (S/../.././02|L/../../6/03) -m admin@example.com EOF sysrc smartd_enable="YES" service smartd start
This runs short SMART tests daily at 2 AM and long tests every Saturday at 3 AM.
Disaster Recovery
ZFS Pool Import
If you move disks to a new system:
shzpool import
Lists all discoverable pools. Import by name:
shzpool import tank
ZFS stores metadata on every disk. Even with partial disks, degraded pools can be imported.
GEOM Mirror Recovery
If the system does not boot and the mirror is degraded:
- Boot from FreeBSD install media
- Load the geom module:
kldload geom_mirror - Check mirror status:
gmirror status - If degraded, mount and repair:
mount /dev/mirror/gm0s1a /mnt
Converting GEOM to ZFS
If you want to migrate from GEOM RAID to ZFS:
- Back up all data
- Destroy the GEOM configuration
- Create a new ZFS pool on the raw disks
- Restore data
There is no in-place conversion -- it requires a full data migration.
FAQ
Q: Should I use ZFS or GEOM for a new server?
A: ZFS for almost everything. GEOM is useful for boot mirrors on systems with limited RAM or when you need a raw block device for a non-ZFS filesystem.
Q: Can I expand a RAIDZ vdev?
A: Starting with OpenZFS 2.3 (FreeBSD 14.2+), yes. You can add disks to an existing RAIDZ vdev with zpool attach. On older versions, you must add new vdevs (mirrors or RAIDZ) to the pool.
Q: What is the minimum number of disks for each RAIDZ level?
A: RAIDZ1: 2 disks (1 data + 1 parity). RAIDZ2: 3 disks. RAIDZ3: 4 disks. Practical minimums for decent performance: RAIDZ1: 3-5 disks. RAIDZ2: 5-8 disks. RAIDZ3: 7-12 disks.
Q: How long does a resilver take?
A: ZFS resilvers only used data, not the entire disk. A 50% full 10 TB disk resilvers ~5 TB. On SATA SSDs, expect 200-400 MB/s resilver speed. On spinning disks, 100-150 MB/s. So 5 TB on SATA SSD: 3.5-7 hours.
Q: Can I mix disk sizes in ZFS?
A: Yes, but the vdev uses the capacity of the smallest disk. A mirror of a 2 TB and 4 TB disk gives you 2 TB usable. Replace the smaller disk later to use the full capacity.
Q: Is GEOM RAID faster than ZFS?
A: For raw sequential throughput, GEOM can be slightly faster because it has no checksum overhead or copy-on-write. For real-world workloads with data integrity, ZFS is better. The GEOM speed advantage disappears once you add a filesystem on top.
Q: Should I use a hardware RAID controller with FreeBSD?
A: Generally no. ZFS and GEOM both want direct disk access. Hardware RAID hides the disks behind a virtual device, preventing ZFS from managing redundancy and checksumming. If you have a hardware RAID controller, put it in JBOD/passthrough mode.
Q: How do I check if my disks are healthy?
A: Use smartctl -a /dev/da0 for SMART data. For ZFS, zpool status -v shows checksum errors per disk. Run zpool scrub monthly to proactively detect silent corruption.