Install on the server
# Debian / Ubuntu
sudo apt install nfs-kernel-server
# Fedora / RHEL
sudo dnf install nfs-utils
# Verify
sudo systemctl status nfs-server
Plan the export filesystem
NFSv4 expects a single virtual filesystem root with exported subdirectories bind-mounted under it. Common convention is to use /srv/nfs as the root:
# Create the root + per-export bind mount points
sudo mkdir -p /srv/nfs/{photos,projects,backup}
# Bind-mount real directories under the NFS root
sudo mount --bind /mnt/data/photos /srv/nfs/photos
sudo mount --bind /mnt/data/projects /srv/nfs/projects
sudo mount --bind /mnt/raid/backup /srv/nfs/backup
# Make persistent in /etc/fstab
cat << 'EOF' | sudo tee -a /etc/fstab
/mnt/data/photos /srv/nfs/photos none bind 0 0
/mnt/data/projects /srv/nfs/projects none bind 0 0
/mnt/raid/backup /srv/nfs/backup none bind 0 0
EOF
Configure exports
Edit /etc/exports:
# The pseudo-root (NFSv4 requirement)
/srv/nfs 192.168.1.0/24(rw,sync,no_subtree_check,fsid=root,crossmnt)
# Each export under the root
/srv/nfs/photos 192.168.1.0/24(rw,sync,no_subtree_check,nohide)
/srv/nfs/projects 192.168.1.0/24(rw,sync,no_subtree_check,nohide,no_root_squash)
/srv/nfs/backup 192.168.1.10(rw,sync,no_subtree_check,nohide) # backup server only
/srv/nfs/photos 192.168.1.50(ro,sync,no_subtree_check,nohide) # media VM read-only
Apply:
sudo exportfs -ra # reload exports
sudo exportfs -v # show what's currently exported
sudo systemctl restart nfs-server
The options that matter
- rw / ro — read-write or read-only
- sync — commit writes before replying; safer, slightly slower.
asyncis faster but data can be lost on server crash - no_subtree_check — avoid spurious "stale file handle" errors after renames
- fsid=root — required on the v4 pseudo-root export
- crossmnt — allow clients to see subdirectories that are themselves mounted (e.g. bind mounts under the root)
- nohide — show child exports under the parent (NFSv4 default behavior)
- no_root_squash — allow remote root to act as root (rare; usually leave the default
root_squash) - all_squash — map all remote UIDs to anonymous; useful for "shared dump folder" semantics
- anonuid=1000,anongid=1000 — what UID/GID anonymous maps to
Firewall
NFSv4 only needs TCP port 2049 (no RPC dance like v3). Open it to the trusted LAN:
# nftables (see /tutorials/nftables-modern-linux-firewall.html)
ip saddr 192.168.1.0/24 tcp dport 2049 accept
# Or ufw
sudo ufw allow from 192.168.1.0/24 to any port 2049
# Or firewalld
sudo firewall-cmd --permanent --add-service=nfs
sudo firewall-cmd --reload
Mount on a client
sudo apt install nfs-common
# Manual mount
sudo mkdir -p /mnt/photos
sudo mount -t nfs4 -o noatime,vers=4.2 nas.lab:/photos /mnt/photos
# Verify
df -h /mnt/photos
ls /mnt/photos
Note: with NFSv4 the path is relative to the pseudo-root — not /srv/nfs/photos but just /photos.
Persistent mount via /etc/fstab
# /etc/fstab
nas.lab:/photos /mnt/photos nfs4 noauto,x-systemd.automount,noatime,vers=4.2,_netdev 0 0
noauto + x-systemd.automount = lazy mount only when accessed; less hassle if the NFS server isn't up at boot. _netdev tells systemd to wait for the network before trying.
Per-mount tuning options
# Read-ahead size + reasonable timeouts
sudo mount -t nfs4 nas.lab:/photos /mnt/photos \
-o noatime,vers=4.2,rsize=131072,wsize=131072,timeo=600,retrans=2,hard,intr
# rsize/wsize 128KB (or larger on 10 GbE): faster bulk transfer
# timeo=600: 60 second timeout per request (in tenths of a second)
# hard: keep retrying on timeout (vs soft, which fails the I/O)
# intr: allow Ctrl-C to interrupt stuck I/O (legacy; modern kernel ignores)
UID / GID mapping
NFS by default uses numeric UIDs/GIDs. If amir is UID 1000 on the server but UID 1001 on the client, file ownership gets confused.
Three strategies:
- Match UIDs across hosts. Simplest for homelab; pick one UID/GID per user and use it everywhere.
- idmapd / NFSv4 ID mapping. Both sides agree on a domain; UIDs are mapped via
user@domain. Less common in practice. - all_squash with anonuid. Treat the whole export as anonymous; useful for shared / dump folders.
Kerberos auth (krb5p)
For environments needing real authentication + encryption, NFSv4 supports Kerberos:
# Export with Kerberos privacy (encrypt + auth)
/srv/nfs/sensitive *(rw,sync,sec=krb5p,no_subtree_check)
# Client mount
sudo mount -t nfs4 -o sec=krb5p,vers=4.2 server:/sensitive /mnt/sensitive
Requires a working Kerberos KDC (FreeIPA, Active Directory, or standalone MIT/Heimdal). For most homelab use, sec=sys (default UID-based) on a trusted LAN is fine; Kerberos is for "I can't trust the LAN to be clean."
NFSv4.2 features worth knowing
- Server-side copy —
cpbetween two paths on the same NFS server doesn't round-trip data through the client. - Sparse files — correctly preserve holes (no longer dense-allocated on copy).
- Application data block —
fallocateworks correctly over NFS. - POSIX ACLs —
setfacl/getfaclwork transparently.
Always mount with vers=4.2 on modern clients + servers; gives you the full feature set.
Debugging
# Check exports on server
sudo exportfs -v
sudo cat /proc/fs/nfsd/exports
# Check who's mounted from the server
sudo showmount -a nas.lab # may not work for v4-only; use rpcinfo
# Server-side stats
nfsstat -s
# Client-side stats
nfsstat -c
# Force unmount a stuck NFS mount (last resort)
sudo umount -l /mnt/photos # lazy unmount
# Or
sudo umount -f /mnt/photos # force
NFSv4 vs alternatives
- Samba / SMB — the right pick for Windows / macOS clients. Linux can mount SMB too, but for Unix-to-Unix, NFS is faster + simpler.
- SSHFS — FUSE over SSH; great for ad-hoc remote mounts; slower than NFS; doesn't scale to dozens of mounts.
- iSCSI (see that tutorial) — block storage; one writer per LUN. Different shape.
- CephFS (see that tutorial) — distributed POSIX filesystem; more redundancy, much more setup.
For "one NAS exporting directories to N Linux clients," NFSv4 is the simplest right answer in 2026.