Server Setup: NAS¶
You can find more in depth info in the related guides here.¶
-
This is just how I did it. There are plenty of other ways to do it using different configurations (ZFS, Proxmox, Unraid, TrueNAS, OMV, etc). All options have pros and cons.
-
My NAS is a plain Debian 13 "trixie" server (Docker CE on top, like the rest of the servers) with MergerFS + SnapRAID. It also runs the media stack, so the same box serves files and apps.
MergerFS: Pool drives and read them as one drive. FUSE system. Various configurations, read the official docs. The docs are very literal.
SnapRAID: Nightly parity snapshots of the data drives, so a dead drive can be rebuilt. No striping, easy to expand, straightforward to manage.
Backups are separate. Parity protects against a disk dying, not against deleting a file, ransomware or losing the whole box. Configs, databases and the small irreplaceable folders go to borgmatic; the media library relies on parity only, by choice.
The layout¶
| Disk(s) | Filesystem | Mounted at | Role |
|---|---|---|---|
| NVMe (boot) | ext4 | / |
OS, SnapRAID content file, borg repos the other servers back up into |
| SATA SSD | ext4 | /mnt/appdata |
Container configs and databases (fast, outside the pool and parity) |
| 2 × 12 TB HDD | XFS | /mnt/disk1, /mnt/disk2 |
Data disks, pooled by MergerFS at /mnt/storage |
| 1 × 12 TB HDD | XFS | /mnt/parity1 |
SnapRAID parity (never in the pool) |
| 3 TB HDD | ext4 | /mnt/frigate |
Camera recordings: constant writes, so outside the pool and parity |
Why the extra disks sit outside the pool: anything that writes all day (databases, NVR recordings) would make every SnapRAID sync large and would have to be stopped during it. Keeping them on their own disks keeps parity for the data that changes slowly (media, documents).
Mount points¶
Naming Convention¶
The examples on these pages use /mnt/disk1, /mnt/disk2, /mnt/parity1, which is the easiest to read.
On my own box I mount each drive at its type and serial instead, so a mount point tells you which physical
disk to pull:
HDDs: /mnt/hdd
SATA SSDs: /mnt/ssd
NVME/M2: /mnt/nvme
REMOTE: /mnt/remote
Example¶
$ ls -lh /mnt/hdd/
total 8K
d--------- 2 root root 4.0K Apr 14 15:58 10T-01234567
d--------- 2 root root 4.0K Apr 12 20:51 20T-12345678
# You can find the serial number of a drive using lsblk
$ lsblk -d -o NAME,PATH,SIZE,SERIAL
NAME PATH SIZE SERIAL
sda /dev/sda 9.1T 01234567
sdb /dev/sdb 18.2T 12345678
nvme0n1 /dev/nvme0n1 953.9G ABCDEFGH
Serial naming and the parity disk
If the parity disk lives under /mnt/hdd/ too, you can't pool with a glob like /mnt/hdd/*, because that
would put the parity disk into the pool. List the data branches explicitly in fstab (see below).
1. Drive Preparation¶
Before pooling, drives must be partitioned and formatted.
- Note: Identify your disks carefully using
lsblk -o NAME,SIZE,SERIALorsudo fdisk -l. - Strategy: XFS for the big data and parity disks, ext4 for the SSD and smaller disks. Both are in the Debian installer and well supported by SnapRAID.
# Tools used below (parted isn't always installed on a minimal Debian)
sudo apt update && sudo apt install -y parted xfsprogs attr
Formatting SSD as ext4¶
Back up first
wipefs, parted and mkfs erase everything on the target disk. Back up any data you want to keep and
double-check the device name with lsblk before running these commands.
# 1. Create a GPT partition table (Destructive!)
sudo wipefs -a /dev/sdb
sudo parted -s /dev/sdb mklabel gpt
# 2. Create the partition (100% of disk)
sudo parted -s -a opt /dev/sdb mkpart primary ext4 0% 100%
# 3. Format to EXT4
# -m 0: Reserve 0% for root (max space for data)
# -L: Label the disk to make it easy to identify
sudo mkfs.ext4 -m 0 -L appdata /dev/sdb1
Formatting 3x HDDs as XFS¶
# Drive 1 (sdb)
# wipe fs
sudo wipefs -a /dev/sdb
# create GPT partition table
sudo parted -s /dev/sdb mklabel gpt
# create partition
sudo parted -s /dev/sdb mkpart primary xfs 0% 100%
# list partition
sudo fdisk -l /dev/sdb
# format partition
sudo mkfs.xfs -f -L "disk1" /dev/sdb1
# Drive 2 (sdc)
sudo wipefs -a /dev/sdc
sudo parted -s /dev/sdc mklabel gpt
sudo parted -s /dev/sdc mkpart primary xfs 0% 100%
sudo fdisk -l /dev/sdc
sudo mkfs.xfs -f -L "disk2" /dev/sdc1
# Drive 3 (sdd)
sudo wipefs -a /dev/sdd
sudo parted -s /dev/sdd mklabel gpt
sudo parted -s /dev/sdd mkpart primary xfs 0% 100%
sudo fdisk -l /dev/sdd
sudo mkfs.xfs -f -L "parity1" /dev/sdd1
-
Get UUIDs
-
Output will be:
lrwxrwxrwx 1 root root 10 Dec 7 05:34 <DISK1_UUID> -> ../../sdb1 lrwxrwxrwx 1 root root 15 Dec 7 04:33 <BOOT_UUID> -> ../../nvme0n1p2 lrwxrwxrwx 1 root root 10 Dec 7 05:34 <DISK2_UUID> -> ../../sdc1 lrwxrwxrwx 1 root root 10 Dec 7 05:34 <APPDATA_UUID> -> ../../sda1 lrwxrwxrwx 1 root root 10 Dec 7 05:34 <PARITY1_UUID> -> ../../sdd1
Always mount by UUID=, never /dev/sdX: the letters can change between boots
(see Disk letters change between boots).
Mounting¶
-
Create Mount points:
-
Edit fstab
- Scroll to bottom of fstab and add:
# --------------------------------------------------------- # DATA + PARITY DRIVES (XFS) - 12TB WD Reds # --------------------------------------------------------- UUID=<DISK1_UUID> /mnt/disk1 xfs defaults,noatime 0 0 UUID=<DISK2_UUID> /mnt/disk2 xfs defaults,noatime 0 0 UUID=<PARITY1_UUID> /mnt/parity1 xfs defaults,noatime 0 0 # --------------------------------------------------------- # FAST STORAGE (EXT4) - SSD # --------------------------------------------------------- UUID=<APPDATA_UUID> /mnt/appdata ext4 defaults,noatime 0 0 -
NOTE: MUST RUN AFTER EDITING fstab¶
Verify¶
-
Test this BEFORE reboot - no output is good
-
Check correct sizing:
You should see:
-
disk1,disk2,parity1: ~11T -
appdata: the SSD's size -
Check ownership:
-
If owner listed as
root(and doesnt need to be) run:
2. Install MergerFS¶
Read the official docs. There is a lot to go over with mergerFS. Debian 13 ships a recent mergerfs (2.40), so the
distro package is fine; upstream also publishes .deb files on GitHub if you need a newer release.
Configure Pool in Fstab
Add this line to the bottom of /etc/fstab (this is the line my NAS runs today):
# MergerFS data pool: the data disks only, never the parity disk
/mnt/disk1:/mnt/disk2 /mnt/storage fuse.mergerfs cache.files=per-process,cache.files.process-names=rtorrent|qbittorrent-nox|TorrentCreation,category.create=mspmfs,func.getattr=newest,dropcacheonclose=false,minfreespace=100G,moveonenospc=true,fsname=mergerfs 0 0
- cache.files=per-process: Page caching off for everything except the named processes. Torrent clients use
mmap, which needs page caching; withcache.files=offthey error out. Everyone else (Plex, the *arrs, Samba) gets uncached I/O. - category.create=mspmfs: Most Shared Path, Most Free Space. Of the branches that already share the most of the file's parent path, writes to the one with the most free space. Keeps a show's season on one disk.
- func.getattr=newest: Returns file attributes from the file with the newest mtime. Critical for apps like Plex/Kodi to detect changes.
- minfreespace=100G: A branch with less than 100 GB free gets no new files. When every branch is below it, new files fail, so the free-space alert watches this (below).
- moveonenospc=true: If a write runs out of space on one branch, mergerfs moves the file to another branch and carries on.
Full explanation on the MergerFS page.
C. Activate
sudo mkdir -p /mnt/storage
sudo systemctl daemon-reload
sudo mount -a
df -h /mnt/storage # size = sum of the data disks
3. SnapRAID¶
SnapRAID calculates parity (like RAID 5) but does it on a schedule (Snapshot), not in real-time. Ideally suited for media servers where files rarely change.
A. Install SnapRAID Debian 13 has SnapRAID 12.4 in its main repo, which is what runs here. Upstream is newer (see the SnapRAID page if you want to build it from source).
B. Configure /etc/snapraid.conf
Backup the default and create your own: sudo nano /etc/snapraid.conf
# 1. Parity Location (The dedicated large drive)
parity /mnt/parity1/snapraid.parity
# 2. Content Files (Index of all your files)
# One copy per physical disk: the boot NVMe plus every data disk
content /var/snapraid/snapraid.content
content /mnt/disk1/snapraid.content
content /mnt/disk2/snapraid.content
# 3. Data Disks (the pool branches, not the pool itself)
data d1 /mnt/disk1
data d2 /mnt/disk2
# 4. Excludes (Temp files/Trash)
exclude *.unrecoverable
exclude /tmp/
exclude /lost+found/
C. Initialize Run the first sync to calculate parity. This will take hours depending on data size.
4. Automation & Maintenance¶
SnapRAID is manual by default. You must automate it to stay protected, and you must notice when the automation stops working. My first setup had a plain cron job whose sync was blocked by the "too many deleted files" safety check for a month, and its only notification (a Discord webhook) failed silently because of a DNS problem.
What runs now:
- snapraid-aio-script from root's crontab, nightly: diff, sync if the change counts are sane, then scrub 5% of the array. It pauses the containers for the run.
- A small wrapper around it that stops Plex cleanly instead of pausing it, and reports every run to an Uptime Kuma push monitor (dead-man switch) and ntfy.
The details, including why Plex is stopped rather than paused, are on the SnapRAID page.
Disk health and free space are watched separately:
smartdruns daily short and monthly long self-tests, and sends problems to ntfy (see Disk Management).- An hourly free-space check alerts on the pool's free GiB, not its percentage: mergerfs refuses new files below
minfreespace, and a big pool can be 95% full with plenty of room. The individual branches and the parity disk are skipped, since they run near-full by design.
Troubleshooting¶
- "Disk is full" during sync: Your parity drive must be equal to or larger than your single largest data drive.
- Data Recovery: If
disk1fails, replace it, mount to/mnt/disk1, and runsnapraid -d d1 -l fix.log fix. Full procedure on the SnapRAID page. - A drive failing its SMART long test: Storage lessons learned.