Quick Start (5 Min)
Six months ago, our development team needed shared storage for a CI pipeline spitting out 80GB of Docker images and build artifacts daily. Instead of wrestling with Ceph or GlusterFS, I stood up a dedicated NFS node. That single server now handles roughly 65,000 file transactions an hour without a hitch. Here is how to get a clean server-client pair running in five minutes.
Step 1: Install and Configure the NFS Server
First, install the server daemon. On Ubuntu or Debian:
sudo apt update
sudo apt install -y nfs-kernel-server
On AlmaLinux, Rocky Linux, or RHEL:
sudo dnf install -y nfs-utils
sudo systemctl enable --now nfs-server
Create your export directory. For testing, grant ownership to the unprivileged nobody user:
sudo mkdir -p /srv/nfs/shared
sudo chown -R nobody:nogroup /srv/nfs/shared
sudo chmod 755 /srv/nfs/shared
Open your exports table:
sudo nano /etc/exports
Add this export definition at the bottom, swapping in your actual client subnet (such as 192.168.1.0/24):
/srv/nfs/shared 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)
Export the shares and refresh the running daemon:
sudo exportfs -rav
sudo systemctl restart nfs-kernel-server
Step 2: Connect from the Client Node
Install the client utilities on the machine that needs the storage:
# Debian / Ubuntu
sudo apt install -y nfs-common
# RHEL / AlmaLinux
sudo dnf install -y nfs-utils
Query the server (assumed here at 192.168.1.50) to verify the export is visible:
showmount -e 192.168.1.50
Mount the export to a local directory:
sudo mkdir -p /mnt/shared
sudo mount 192.168.1.50:/srv/nfs/shared /mnt/shared
Confirm the connection by running df -h /mnt/shared. You should see the remote volume mounted with its total capacity.
Architecture and Export Directives
Modern Linux distros default to NFSv4 (specifically v4.1 or v4.2). Unlike NFSv3, which depended on rpcbind and sprayed traffic across arbitrary high ports, NFSv4 talks exclusively over TCP port 2049. That simplifies firewall rules immediately. It also introduces stateful sessions, pseudo-filesystems, and optional Kerberos authentication.
Inside the /etc/exports Directives
The flags in /etc/exports control where your system lands on the safety-versus-speed spectrum:
rwvsro: Allows read-write or forces read-only access.sync: Acknowledges write requests only after data hits physical disk. It prevents data loss during sudden power outages. Always keep this on production storage.async: Acknowledges writes as soon as data reaches the server’s RAM buffer. Throughput jumps dramatically, but a power cut will corrupt unwritten files.no_subtree_check: Skips parent directory checks when accessing subfolders. Leaving this enabled causes timeouts during heavy file renames. Always disable it.root_squash: Maps client UID 0 (root) to thenobodyaccount. This keeps a compromised client root user from wiping your server’s root filesystem. Useno_root_squashonly when clients run trusted backup jobs requiring full root access.all_squashwithanonuid/anongid: Collapses every client connection into one specified user ID. This pattern is ideal for shared application uploads.
Tuning Client Mount Options for Throughput
Default mounts work fine for light text editing. But run sequential backups over a 10GbE interface, and defaults will cap your speeds well below wire rate. Adjusting transfer buffers and caching flags fixes that.
High-Performance Mount Command
Here is the mount command we run across our production nodes:
sudo mount -t nfs -o proto=tcp,nfsvers=4.2,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noatime 192.168.1.50:/srv/nfs/shared /mnt/shared
Why These Flags Matter
rsize=1048576,wsize=1048576: Increases chunk sizes to 1MB. Older defaults often negotiate 32KB or 64KB buffers. In our lab testing, bumping 64KB buffers to 1MB jumped large-file transfer rates on a 1GbE connection from 48 MB/s straight to the line limit of 112 MB/s.noatime: Stops the server from recording an access timestamp every time a process reads a file. In build pipelines reading thousands of source files repeatedly, this cuts disk metadata IOPS by roughly 30%.hardvssoft: Never usesofton data volumes. If the network drops, asoftmount throws I/O errors straight to the application, which corrupts SQLite databases and binary files. Ahardmount pauses process I/O until connectivity returns, safeguarding your data.timeo=600andretrans=2: Sets a 60-second recovery window (specified in tenths of a second) before resending unacknowledged packets. It gives busy NVMe and SATA arrays enough breathing room during flush spikes.
Persistent Mounts via /etc/fstab
Append the mount to /etc/fstab so it reconnects after system restarts. The _netdev parameter is essential: it instructs systemd to delay the mount until network interfaces are fully up.
192.168.1.50:/srv/nfs/shared /mnt/shared nfs nfsvers=4.2,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noatime,_netdev 0 0
Test your fstab syntax immediately without rebooting:
sudo umount /mnt/shared
sudo mount -a
findmnt /mnt/shared
Field Notes and Troubleshooting
1. Lock Down the Firewall
Standard NFS traffic is unencrypted. Restrict access strictly to private IP ranges. If you use UFW on Ubuntu:
sudo ufw allow from 192.168.1.0/24 to any port 2049 proto tcp
sudo ufw status numbered
For firewalld on RHEL or AlmaLinux:
sudo firewall-cmd --permanent --zone=internal --add-service=nfs
sudo firewall-cmd --permanent --zone=internal --add-source=192.168.1.0/24
sudo firewall-cmd --reload
2. Aligning UIDs and GIDs
Mismatched user IDs cause 90% of all NFS permission headaches. NFS passes raw numeric IDs across the wire, not usernames. If user runner has UID 1002 on your client, the server enforces permissions for whichever account owns UID 1002 locally. You have two ways to resolve this:
- Create matching UIDs and GIDs across both systems.
- Force all traffic into a shared user in
/etc/exportswithall_squash,anonuid=1001,anongid=1001.
3. Clearing Stale File Handles
If an exported disk is rebooted or an export path changes, client commands stall with Stale file handle. Standard unmounts hang indefinitely. Break the lock with a lazy unmount:
sudo umount -l /mnt/shared
Once detached, remount with sudo mount -a.
4. Tracking Bottlenecks with nfsstat
Find out whether latency comes from network retransmissions or slow server disks using nfsstat:
# Client statistics
nfsstat -c
# Server statistics
nfsstat -s
Check the retrans metric under client RPC. If that number climbs during active transfers, your switch or NIC is dropping frames.

