How to Build a Minimal ZFS NAS Without Synology, QNAP, TrueNAS (2024)
Guide: Constructing a Minimalist ZFS NAS (2024 Edition)
Avoiding the Bloat of Synology, QNAP, and TrueNAS
If your goal is a straightforward Network Attached Storage (NAS) and you have no need for a complex Graphical User Interface (GUI), setting up a ZFS dataset and sharing it via Samba is remarkably simple.
🎯 Scope & System Requirements
The following specifications define the environment used for this build.
| Component | Specification |
|---|---|
| RAID Level | RAIDZ1 (Single Drive Redundancy) |
| Operating System | Debian 12 Bookworm |
| Encryption | |
| ZFS Version | OpenZFS, zfs-2.1.1 |
| CPU | 4 Cores (Cheap Xeon Server CPUs recommended) |
| RAM | 16 GB ECC RDIMM |
| Storage | 4 x 4TB NVMe SSDs |
| Backups | Not covered (Suggested: ZFS Backup Scheduler) |
| Skill Level | Beginner / Easy |
Note: This guide serves as personal documentation for future reference, but it is open for anyone to adapt for their own hardware.
🧠 The Philosophy: Why Minimalist?
Many users gravitate toward TrueNAS because it is marketed as an enterprise-grade software suite. While powerful, it often introduces a mismatch between the user's actual needs and the provided features.
The core motivations for this approach are:
- Simplicity: Avoiding "bells and whistles" that add complexity without value.
- Knowledge: Building a system where you understand every single configuration point.
- Independence: Reducing reliance on third-party management layers.
🌟 The "Hidden" Superpower of ZFS
There is a critical feature of ZFS that is rarely highlighted: The filesystem is entirely self-contained.
All configuration data and pool metadata are stored directly on the disks. This means:
- If your OS drive is nuked, your data remains safe.
- You can move your disks to a completely different machine.
- Simply install ZFS on the new host and run
zfs importto recover everything.
Whether you have a RAIDZ2 (RAID 6) with 6 disks or a simple mirror, the disks carry their own identity. As long as the physical hardware is intact, your data is portable.
🛠️ Step 1: Disk Identification and Organization
First, identify the available drives on your Linux system using the following command:
lsblk -d -o TRAN,NAME,TYPE,MODEL,SERIAL,SIZE
Example Output:
[root@sys ~]# lsblk -d -o TRAN,NAME,TYPE,MODEL,SERIAL,SIZE
TRAN NAME TYPE MODEL SERIAL SIZE
sda disk Virtual disk 40G
nvme nvme5n1 disk Samsung SSD 990 PRO 4TB XXXXXXXXXXXXXXX 3.6T
nvme nvme2n1 disk Samsung SSD 990 PRO 4TB XXXXXXXXXXXXXXX 3.6T
nvme nvme6n1 disk Samsung SSD 990 PRO 4TB XXXXXXXXXXXXXXX 3.6T
nvme nvme3n1 disk Samsung SSD 990 PRO 4TB XXXXXXXXXXXXXXX 3.6T
Mapping via Disk IDs
To avoid issues where drive letters (like sda or nvme0n1) change after a reboot or hardware swap, we use the unique IDs found in /dev/disk/by-id.
[root@sys ~]# ls -lh /dev/disk/by-id
total 0
lrwxrwxrwx. 1 root root 10 May 10 09:34 dm-name-rhel-root -> ../../dm-0
lrwxrwxrwx. 1 root root 10 May 10 09:34 dm-name-rhel-swap -> ../../dm-1
lrwxrwxrwx. 1 root root 13 May 10 09:34 nvme-Samsung_SSD_990_PRO_4TB_XXXXX -> ../../nvme2n1
lrwxrwxrwx. 1 root root 13 May 10 09:34 nvme-Samsung_SSD_990_PRO_4TB_XXXXX -> ../../nvme5n1
lrwxrwxrwx. 1 root root 13 May 10 09:34 nvme-Samsung_SSD_990_PRO_4TB_XXXXX -> ../../nvme3n1
lrwxrwxrwx. 1 root root 13 May 10 09:34 nvme-Samsung_SSD_990_PRO_4TB_XXXXX -> ../../nvme6n1
Creating Friendly Aliases (Optional)
You can create a mapping file at /etc/zfs/vdev_id.conf to give these long IDs shorter aliases:
# Edit the config file
[root@sys ~]# vim /etc/zfs/vdev_id.conf
# Add the following mappings:
alias nvme0 /dev/disk/by-id/nvme-Samsung_SSD_990_PRO_4TB_XXXXXXXXXXXXXXX
alias nvme1 /dev/disk/by-id/nvme-Samsung_SSD_990_PRO_4TB_XXXXXXXXXXXXXXX
alias nvme2 /dev/disk/by-id/nvme-Samsung_SSD_990_PRO_4TB_XXXXXXXXXXXXXXX
alias nvme3 /dev/disk/by-id/nvme-Samsung_SSD_990_PRO_4TB_XXXXXXXXXXXXXXX
After saving, apply the changes:
udevadm trigger
Verification:
ls -lh /dev/disk/by-vdev should now show your aliases (nvme0, nvme1, etc.) pointing to the correct physical disks.
Pro Tip: While aliases are convenient for setup, they are not required for the pool to function. If you lose your
vdev_id.conffile, ZFS still knows which disks belong to the pool because it uses the hardware serial numbers.
🏗️ Step 2: Creating the ZPOOL
For this build, we are implementing RAIDZ1, which allows for the failure of one drive without data loss.
Installation
Install the ZFS tools. On RHEL 9, for example:
dnf install zfs
The Importance of ashift
When creating the pool, you must set the ashift value. This defines the sector size.
Many modern SSDs (like the Samsung 990 Pro) use 4KB sectors, but report 512B for compatibility.
Using LaTeX to represent the calculation:
Setting ashift=12 is critical for performance; if you miss this step during creation, you cannot change it later without destroying the pool.
Execution
# Verify aliases
[root@sys ~]# ls /dev/disk/by-vdev
nvme0 nvme1 nvme2 nvme3
# Create the pool (s16z1 = 16TB total raw, RAIDZ1)
[root@sys ~]# zpool create -o ashift=12 s16z1 raidz1 nvme0 nvme1 nvme2 nvme3
# Check the status
[root@sys ~]# zpool status s16z1
Expected Status Output:
pool: s16z1
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
s16z1 ONLINE 0 0 0
raidz1-0 ONLINE 0 0 0
nvme0 ONLINE 0 0 0
nvme1 ONLINE 0 0 0
nvme2 ONLINE 0 0 0
nvme3 ONLINE 0 0 0
errors: No known data errors
📊 Summary Visualization
Data Architecture
Final Checklist
- Install OpenZFS
- Identify disks via
/dev/disk/by-id - (Optional) Setup
vdev_id.conf - Create pool with
ashift=12 - Verify
zpool status
