MergerFS Configuration Guide¶
Overview¶
MergerFS is a union filesystem designed to simplify storage management. It pools multiple storage devices (branches) into a single unified directory (mount point).
Unlike RAID, MergerFS does not stripe data across drives. Files exist intact on the individual backing drives.
- Flexibility: Drives of different sizes, filesystems, and speeds can be mixed.
- Resilience: If one drive fails, only the data on that specific drive is lost; the rest of the pool remains accessible.
- Dynamic: Drives can be added or removed from the pool without rebuilding the array.
Installation¶
Debian 13 ships mergerfs 2.40 in its main repo, which is what my NAS runs. Upstream publishes newer .deb builds on
GitHub if you need a feature from a later release.
For latest versions/source: GitHub Releases
Branch Setup & Preparation¶
Important
Never write data directly to the /mnt/disk1 folders if you can help it. Always write to /mnt/storage. If you modify files on the backing disks directly while MergerFS is running, you might see cache inconsistencies until you remount.
Before creating the pool, the underlying mount points (branches) should be organized and secured.
1. Naming Convention¶
Using a consistent naming structure helps identify drive types within the pool.
- HDDs:
/mnt/hdd/Serial-Number - SATA SSDs:
/mnt/ssd/Serial-Number - NVMe:
/mnt/nvme/Serial-Number - Remote Shares:
/mnt/remote/Share-Name
Example Layout:
2. Permissions & Locking (Critical)¶
mount points¶
To ensure the directory is only used as a point to mount another filesystem it is good to lock it down as much as possible. Be sure to do this before mounting a filesystem to it.
Run these commands for each underlying drive mount point:
# 1. Set ownership to root
sudo chown root:root /mnt/hdd/10T-XYZ
# 2. Remove read/write/execute permissions (makes it inaccessible directly)
sudo chmod 0000 /mnt/hdd/10T-XYZ
# 3. Mark as a specific branch mount (prevents mounting if drive is missing)
sudo setfattr -n user.mergerfs.branch_mounts_here /mnt/hdd/10T-XYZ
The extended attribute user.mergerfs.branch_mounts_here is used by the branches-mount-timeout option to recognize whether or not a mergerfs branch path points to the intended filesystem.
The chattr is likely to only work on EXT{2,3,4} filesystems but will restrict even root from modifying the directory or its content but is still able to be a mount point.
mounted filesystems¶
For those new to Linux, intending to be the primary individual logged into the system, or simply want to simplify permissions it is recommended to set the root of mounted filesystems like /tmp/ is set to. Owned by root, ugo+rwx and sticky bit set.
This must be done after mounting the filesystem to the target mount point.
$ sudo chown root:root /mnt/hdd/10T-SERIALNUM
$ sudo chmod 1777 /mnt/hdd/10T-SERIALNUM
$ sudo setfattr -n user.mergerfs.branch /mnt/hdd/10T-SERIALNUM
Pool Configuration¶
1. Create Pool Directory¶
Info
mergerfs quick-start settings
-cache.files=off
-category.create=pfrd
-func.getattr=newest
-dropcacheonclose=false
Create the unified mount point where applications will access the data.
2. Command Line Test¶
Test the pool creation manually before making it persistent.
Note
These settings are just what I used. Your situation may benefit from different layout. Read the docs to see what would work best.
sudo mergerfs -o cache.files=per-process,cache.files.process-names='rtorrent|qbittorrent-nox|TorrentCreation',category.create=mspmfs,func.getattr=newest,dropcacheonclose=false,minfreespace=100G,moveonenospc=true /mnt/disk1:/mnt/disk2 /mnt/storage
sudo umount /mnt/storage # done testing
3. Persistence (/etc/fstab)¶
Add the configuration to /etc/fstab to ensure the pool mounts at boot.
The options my pool runs with:
| Option | Why |
|---|---|
cache.files=per-process + cache.files.process-names (the torrent client's process names, see the fstab line) |
Page caching only for the torrent client. Torrent clients use mmap, which needs the page cache; with cache.files=off they fail on the pool. Everything else (Plex, the *arrs, Samba) gets uncached I/O, so RAM isn't spent caching the same media twice. rtorrent and qbittorrent-nox are mergerfs's default list; TorrentCreation adds qBittorrent's "create torrent" job, which runs under its own process name. |
category.create=mspmfs |
Most Shared Path, Most Free Space. Of the branches that already have the file's parent directory, write to the one with the most free space (falls back to most-free-space if the path is new). Keeps a show or album together on one disk, which keeps a disk failure's damage tidy. |
func.getattr=newest |
Returns file attributes from the file with the newest mtime. Critical for apps like Plex/Kodi to detect changes. |
dropcacheonclose=false |
Only matters with caching on; leave it off for the torrent client's sake. |
minfreespace=100G |
A branch with less than 100 GB free gets no new files. When every branch is below it, new files fail, so alert on the pool's free space (see below). |
moveonenospc=true |
If a write hits "no space" on one branch, move the file to a branch with room and carry on. |
fsname=mergerfs |
Shows up as mergerfs in df and findmnt. |
Add to /etc/fstab:
# <branches> <mountpoint> <type> <options> <dump> <pass>
/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
Explicit branches vs a glob
/mnt/disk* works when the parity disk is mounted somewhere else (/mnt/parity1). If you mount every
HDD under one directory (for example /mnt/hdd/<serial>), list the data branches explicitly, separated by
:, or the parity disk ends up in the pool.
4. Verification¶
Reload the system daemon and mount the pool.
# Verify syntax
sudo findmnt --verify
# Reload fstab
sudo systemctl daemon-reload
# Mount all
sudo mount -a
# Check size (Should equal sum of all drives)
df -h
Docker Integration¶
When configuring containers, always use the unified /mnt/storage path. Never bind volumes to the individual disks (e.g., /mnt/disk1), as this bypasses MergerFS logic.
Example compose.yaml:
services:
plex:
image: linuxserver/plex # pin a version tag in production
volumes:
# Correct: Uses the pool
- /mnt/storage/media/movies:/movies
- /mnt/storage/media/tv:/tv
# INCORRECT: Do not map directly to backing disks
# - /mnt/disk1/media/movies:/movies
Maintenance & Troubleshooting¶
Handling Cached/Deleted Files¶
If a file is deleted from the pool but appears to persist (ghost file), or if changes aren't reflecting immediately, the filesystem cache may need clearing.
- Note: Deleting a file from the pool removes it from the underlying drive.
Common Issues¶
- Permissions: Run MergerFS as
root. Running as a standard user can cause unpredictable behavior. - Pool filling up: watch free GiB on
/mnt/storage, not percent. Withminfreespace=100Gthe pool stops taking new files long beforedfsays 100%. My check warns at 500 GiB and goes critical at 250 GiB free, and ignores the individual branches. - Missing Files: If directories appear missing or permissions seem erratic, ensure the permissions on the underlying physical drives are identical. Use
mergerfs.fsckto audit synchronization. - Scanner Lag (Plex/Kodi): If media scanners miss new files, ensure
func.getattr=newestis enabled in the options. This forces the pool to report the most recent modification time found across all branches.