NFS (Network File System) lets you share directories between several
Linux servers, as if they were local. You mount a remote folder with a
simple mount command and your applications access it transparently. This
guide takes you from installing an NFSv4.2 server on Ubuntu 24.04 to mounting
on the client side, with performance options, Kerberos security and
troubleshooting; every command was tested in a lab.
What you will learn
- How NFS works: protocol, versions, client-server architecture
- Installation:
nfs-kernel-serverandnfs-commonon Debian and Ubuntu,nfs-utilson Rocky, RHEL and AlmaLinux - Export configuration:
/etc/exports,rw,ro,syncoptions - Client-side mounting:
mountcommand,/etc/fstab, NFSv4.2 negotiation - Permissions:
root_squash,all_squash,idmapd - Performance:
rsize,wsize,nconnect, benchmarks - Kerberos security:
krb5,krb5i,krb5p - Troubleshooting: frequent errors and tested fixes
What is NFS?
NFS (Network File System) is a network protocol that lets a server share directories with other machines on the network. Created in 1984 by Sun Microsystems, it has become a standard in the Unix/Linux world.
Evolution of versions
| Version | Year | Main contributions |
|---|---|---|
| NFSv2 | 1989 | First public version, files limited to 2 GB |
| NFSv3 | 1995 | Large files, asynchronous writes, better cache |
| NFSv4 | 2003 | Stateful protocol, a single port (2049/tcp), native Kerberos security |
| NFSv4.1 | 2010 | Sessions, pNFS (parallel NFS) for performance |
| NFSv4.2 | 2016 | Server-side copy, sparse files, security labelling |
Why use NFS?
NFS remains relevant for several concrete use cases:
- Centralise data: a single place to back up instead of copying files to each machine
- Share home directories: users find their files on any workstation
- Distribute configurations: scripts, certificates, shared files reachable from the whole fleet
- Provide storage to Kubernetes: NFS serves as a backend for PersistentVolumes with shared read/write (RWX)
NFS vs alternatives
The line to look at first is Protocol, because it separates two incompatible families. NFS, Samba and SSHFS expose files: the server manages the file system, several clients can write to the same directory. iSCSI exposes a raw block device: the client formats it itself, and two clients mounting it at the same time without a cluster file system corrupt the data. The next columns only make sense once that family is chosen.
| Criterion | NFS | iSCSI | Samba (CIFS) | SSHFS |
|---|---|---|---|---|
| Protocol | Files | Block | Files | Files |
| Port | 2049/tcp | 3260/tcp | 445/tcp | 22/tcp |
| Native clients | Linux, macOS | Linux, Windows | Windows, Linux | Linux, macOS |
| Performance | Good | Excellent | Average | Low |
| Complexity | Low | Moderate | Low | Very low |
| Use case | Linux shares | Virtual disks | Windows shares | Occasional access |
Prerequisites
- 2 Ubuntu 24.04 machines (physical or virtual)
- Network connectivity between the two (same subnet or routed)
- root or
sudoaccess on both machines - Port 2049/tcp open between server and client
To reproduce this lab, two KVM VMs are enough: a server (192.168.122.50) and a client (192.168.122.225).
Installing the NFS server
The NFS server is built into the Linux kernel; the package only brings the
user-space tools that drive it. Its name changes with the distribution
family, and that is the most frequent trap: on Debian and Ubuntu it is called
nfs-kernel-server, on Rocky, RHEL and AlmaLinux nfs-utils, which contains
both the server and the client. A dnf install nfs-kernel-server
therefore fails with "No matching Packages to list": this package does not
exist in that family.
sudo apt updatesudo apt install nfs-kernel-serversudo dnf install nfs-utilsThe service starts automatically. Check its state:
sudo systemctl status nfs-serverExpected result: Active: active (exited). The exited status is normal
because the NFS server runs through kernel threads, not a classic process.
Configure the exports
The /etc/exports file defines which directories the server shares and
with which permissions. Each line describes an export: the local path,
the authorised network and the options in parentheses.
Create the shared directories
Start by creating the directory structure. With NFSv4, you define an
export root (here /srv/nfs) that serves as the single entry point for
all shares:
sudo mkdir -p /srv/nfs/partage /srv/nfs/projetssudo chown 65534:65534 /srv/nfs/partage /srv/nfs/projetsThe owner is given in numeric form on purpose. The anonymous user has
UID 65534 everywhere, but its group does not have the same name in each
family: nogroup on Debian and Ubuntu, nobody on RHEL, Rocky and AlmaLinux,
where nogroup does not exist and where chown nobody:nogroup fails with
invalid group.
Configure /etc/exports
Edit the /etc/exports file to declare your shares:
sudo nano /etc/exports/srv/nfs 192.168.122.0/24(rw,sync,fsid=0,crossmnt,no_subtree_check)/srv/nfs/partage 192.168.122.0/24(rw,sync,no_subtree_check,no_root_squash)/srv/nfs/projets 192.168.122.0/24(ro,sync,no_subtree_check,all_squash)Understand the export options
| Option | Meaning | When to use it |
|---|---|---|
rw | Read and write allowed | Collaborative shares |
ro | Read only | Config distribution, documentation |
sync | Writes to disk before answering the client | Always in production (reliability) |
async | Answers before writing to disk | Performance tests only (risk of loss) |
fsid=0 | Designates the root of the NFSv4 pseudo-filesystem | A single line with this option |
crossmnt | Crosses sub-mounts automatically | On the export root |
no_subtree_check | Disables subtree checking | Recommended (performance + stability) |
no_root_squash | The client's root stays root on the server | Automated deployment, Ansible |
root_squash | The client's root becomes nobody (default) | Production (security) |
all_squash | All users become nobody | Public read-only shares |
anonuid=N / anongid=N | UID/GID used for "squashed" users | Control the permissions of nobody |
Apply the exports
After any change to /etc/exports, apply the changes:
sudo exportfs -raCheck: show the active exports with their options.
sudo exportfs -v/srv/nfs 192.168.122.0/24(rw,sync,wdelay,crossmnt,root_squash,...,fsid=0,...)/srv/nfs/partage 192.168.122.0/24(rw,sync,wdelay,no_root_squash,...,no_subtree_check,...)/srv/nfs/projets 192.168.122.0/24(ro,sync,wdelay,root_squash,...,all_squash,...,no_subtree_check,...)Mount an NFS share on the client side
Client-side mounting assumes the export is already allowed for this address on the server. An export error shows up here, as a refusal, and never as a message on the server side.
Install the NFS client
The client comes down to user-space utilities: the NFS mount code lives in the kernel, it is already present. These packages bring mount.nfs4, the nfs-idmapd daemon that translates identities, and nfsstat. On Debian and Ubuntu, the nfs-common client package is separate from the server package; on Rocky and RHEL, nfs-utils contains both, which explains the identical command in both tabs.
sudo apt updatesudo apt install nfs-commonsudo dnf install nfs-utilsManual mount
Create the mount points and mount the shares:
sudo mkdir -p /mnt/nfs-partage /mnt/nfs-projetssudo mount -t nfs4 192.168.122.50:/partage /mnt/nfs-partagesudo mount -t nfs4 192.168.122.50:/projets /mnt/nfs-projetsCheck: confirm the mount and the negotiated version.
mount | grep nfs4192.168.122.50:/partage on /mnt/nfs-partage type nfs4 (rw,...,vers=4.2,...,proto=tcp,...)192.168.122.50:/projets on /mnt/nfs-projets type nfs4 (ro,...,vers=4.2,...,proto=tcp,...)The result shows vers=4.2: client and server automatically negotiated
NFSv4.2, the most recent version.
Test reading and writing
Everything happens on the client side. Write to the read-write share, then read back:
# On the client: write to the rw shareecho "Données partagées via NFS" | sudo tee /mnt/nfs-partage/test.txt
# Check readingcat /mnt/nfs-partage/test.txtDonnées partagées via NFSCheck that the read-only share does refuse writing:
echo "test" | sudo tee /mnt/nfs-projets/interdit.txttee: /mnt/nfs-projets/interdit.txt: Read-only file systemThe projets share is indeed read-only: the server refuses the write
according to the ro option configured in /etc/exports.
Automatic mount via /etc/fstab
For the shares to mount automatically at boot, add them to /etc/fstab:
192.168.122.50:/partage /mnt/nfs-partage nfs4 defaults,_netdev,nofail 0 0192.168.122.50:/projets /mnt/nfs-projets nfs4 defaults,ro,_netdev,nofail 0 0It is nofail that matters here, and the box below says why: without it,
a server missing at boot degrades the client's boot.
The _netdev option flags a network file system, so that the mount waits
for the network to be available. On an nfs mount, however, it has no
effect: systemd already recognises nfs as a network file system and
orders the mount accordingly.
Check: test the mount without rebooting.
sudo mount -adf -h | grep nfsUseful mount options
| Option | Effect | Recommendation |
|---|---|---|
vers=4.2 | Forces NFSv4.2 | Useful if the server supports several versions |
hard | Retries forever on an outage (default) | Production (no data loss) |
soft | Gives up after a delay | Non-critical batch scripts |
noatime | Does not update the last access date | Performance (fewer writes) |
rsize=262144 | Read block size (bytes) | Increase if bandwidth is available |
wsize=262144 | Write block size (bytes) | Same |
nconnect=4 | Parallel TCP connections | Fast network (10 Gbps+) |
_netdev | Flags a network mount | No effect for nfs: systemd already knows |
nofail | Boot goes on if the server is missing | It is this one that protects the boot |
Managing permissions: UID/GID and idmapd
NFS uses numeric UIDs and GIDs for permissions, and never names. If
user bob has UID 1000 on the server and 1001 on the client, files will show
up with the wrong owner.
Two approaches
The simplest method: make sure each user has the same UID and GID on all machines. It is the recommended approach in a small environment.
# Check a user's UIDid bob# uid=1000(bob) gid=1000(bob) groups=1000(bob)If the UIDs differ, change them:
sudo usermod -u 1000 bobsudo groupmod -g 1000 bobsudo find / -user OLD_UID -exec chown -h 1000 {} \;For larger environments, idmapd resolves user names between client and
server. Configure the same domain on all machines in /etc/idmapd.conf:
[General]Domain = lab.local
[Mapping]Nobody-User = nobodyNobody-Group = nogroupOn RHEL, Rocky and AlmaLinux, the value is nobody, not nogroup: the
file shipped by nfs-utils even carries #Nobody-Group = nobody there.
Restart the service:
sudo systemctl restart nfs-idmapdOptimise performance
These settings are useless until the raw throughput has been measured: without a starting point, no option can be credited with a gain.
Baseline benchmark
Measure throughput before any optimisation to have a basis for comparison:
# Write (100 MB, direct I/O)dd if=/dev/zero of=/mnt/nfs-partage/bench bs=1M count=100 oflag=direct
# Readdd if=/mnt/nfs-partage/bench of=/dev/null bs=1M iflag=directOn our lab (virtio network, same KVM host), the results are:
| Direction | Measured throughput |
|---|---|
| Write | ~135 MB/s |
| Read | ~2.7 GB/s |
Mount tuning
For better throughput, increase the block size and enable parallel connections:
sudo mount -t nfs4 \ -o rsize=1048576,wsize=1048576,noatime,nconnect=4 \ 192.168.122.50:/partage /mnt/nfs-partageEquivalent in /etc/fstab:
192.168.122.50:/partage /mnt/nfs-partage nfs4 rsize=1048576,wsize=1048576,noatime,nconnect=4,_netdev,nofail 0 0System tuning
Increase the kernel's network buffers to support large transfers:
net.core.rmem_default = 262144net.core.wmem_default = 262144net.core.rmem_max = 16777216net.core.wmem_max = 16777216sudo sysctl -p /etc/sysctl.d/99-nfs-perf.confMonitoring
The figure that gives away an NFS problem is not throughput, it is the rate of retransmissions. Each retransmission means an RPC request stayed unanswered long enough for the client to resend it: beyond a few tenths of a percent, look at the network or at an overloaded server, not at the mount options. Beware of the scope of each command: mountstats and nfsstat -c only read client-side counters, nfsstat -s only works on the server. These counters are cumulative since boot: compare two readings taken some time apart rather than a single value.
| Command | Use |
|---|---|
nfsstat -s | Server-side NFS statistics (RPC calls) |
nfsstat -c | Client-side NFS statistics |
mountstats | Per-mount details (latency, retransmissions) |
iostat -x 1 | Real-time disk activity |
# See the detailed statistics of a mountmountstats /mnt/nfs-partageSecure NFS with Kerberos
By default, NFS uses the sec=sys mode (AUTH_SYS), which man nfs sums
up bluntly: no cryptographic security. Authorisation rests on two weak
elements: the IP address declared in /etc/exports and the UID the
client assigns itself in each request. The server trusts that UID without
checking it: an attacker with root rights on a machine of the network can
forge any UID and read the matching files. Even root_squash only protects
UID 0, not the others.
Concrete consequence: without Kerberos, NFS is only safe on a trusted, isolated network, so a dedicated VLAN and a strict firewall. For any shared or exposed network, Kerberos adds strong authentication and, optionally, encryption of data in transit.
The three security levels
These three modes stack: krb5i adds integrity to krb5 authentication, and krb5p adds encryption on top of integrity. The distinction that matters in practice is between krb5i and krb5p: the first guarantees that an attacker on the network cannot modify your data in transit, but they can still read it. Only krb5p (privacy) encrypts the payload. The mode is declared on both sides: in /etc/exports on the server, in the sec= option at mount time on the client, and a mismatch ends in a refused mount.
| Level | Option | Authentication | Integrity | Encryption | Performance impact |
|---|---|---|---|---|---|
| Basic | sec=krb5 | Yes | No | No | Low |
| Integrity | sec=krb5i | Yes | Yes | No | Moderate |
| Full | sec=krb5p | Yes | Yes | Yes | Significant (~30–50%) |
Kerberos prerequisites
- A KDC (Key Distribution Center), the central authentication server (MIT Kerberos or FreeIPA)
- Synchronised clocks via NTP on all machines (tolerance ±5 min)
- Working DNS with forward and reverse resolution (Kerberos uses FQDNs)
Server-side configuration
The decisive step is the third one: the keytab. This file holds the key of the nfs/serveur.lab.local principal; it is what proves to the KDC that this server is indeed the one it claims to be. Without a keytab readable by the rpc.gssd daemon, the mount fails with no explicit message on the client side. The chmod 600 is not decorative: a world-readable keytab is equivalent to publishing the service password. Adapt serveur.lab.local and the LAB.LOCAL realm to your domain, Kerberos requires the exact FQDN resolved by forward and reverse DNS.
-
Install the Kerberos packages:
Terminal window sudo apt install krb5-user nfs-kernel-server -
Create an NFS principal in the KDC:
Terminal window kadmin -q "addprinc -randkey nfs/serveur.lab.local" -
Export the key into a keytab:
Terminal window kadmin -q "ktadd -k /etc/krb5.keytab nfs/serveur.lab.local"sudo chmod 600 /etc/krb5.keytab -
Configure the export with Kerberos:
/etc/exports /srv/nfs 192.168.122.0/24(rw,sync,fsid=0,crossmnt,sec=krb5p) -
Restart the server:
Terminal window sudo exportfs -rasudo systemctl restart nfs-server
Client-side configuration
-
Install the packages:
Terminal window sudo apt install krb5-user nfs-common -
Get a Kerberos ticket:
Terminal window kinit utilisateur@LAB.LOCALklist # check the ticket -
Mount with Kerberos:
Terminal window sudo mount -t nfs4 -o sec=krb5p serveur.lab.local:/ /mnt/nfs
Check:
mount | grep nfs4 | grep sec=krb5pIf the line appears, Kerberos encryption is active.
NFS as a Kubernetes backend
NFS holds a special place in Kubernetes: it provides the ReadWriteMany (RWX) access mode, where several nodes mount the same volume read-write. Few backends offer it, most block disks being limited to a single node, which makes NFS a simple way to share data between pods.
Two integrations exist. The csi-driver-nfs
(nfs.csi.k8s.io, version 4.13.x in 2026) is the modern, recommended
approach: dynamic provisioning, snapshots and cloning from an existing NFS
server. The older nfs-subdir-external-provisioner (frozen since early
2023) remains widespread but is barely maintained any more; it creates one
subdirectory per volume.
Two traps come back in clusters. Permissions: if the export is
root_squash, containers running as root and the recursive chown triggered
by fsGroup run into the mapping to the anonymous user; you must control the
containers' UIDs or adjust anonuid. And the point of failure: a single
NFS server becomes a SPOF for all the RWX volumes that depend on it.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
mount hangs or times out | NFS service stopped on the server | sudo systemctl start nfs-server |
mount.nfs4: access denied | Client IP missing from /etc/exports | Check the network in /etc/exports and exportfs -ra |
Permission denied on files | root_squash active (root → nobody) | Use no_root_squash or work without sudo |
Files owned by nobody | Different idmapd domain on client/server | Synchronise /etc/idmapd.conf (same Domain) |
| Mounted as NFSv3 instead of v4 | Client negotiating a low version | Force mount -t nfs4 or vers=4.2 in fstab |
| Degraded performance | Blocks too small (rsize/wsize) | Increase to 1048576 + enable nconnect=4 |
kinit fails | Clock out of sync (>5 min) vs KDC | Synchronise with NTP: sudo systemctl restart chronyd |
| Port 2049 filtered | Firewall blocks NFS traffic | sudo ufw allow from 192.168.122.0/24 to any port 2049 |
Stale file handle | Export changed without exportfs -ra | sudo exportfs -ra then remount on the client |
When NFS is not the right choice
NFS is simple and efficient, but it has limits worth knowing before imposing it everywhere.
- A single server is a SPOF. All clients depend on it. NFS high availability (Pacemaker + DRBD) is possible but complex to operate; if resilience is critical, storage distributed by design such as CephFS is better suited.
- Millions of small files make NFS suffer: each synchronous metadata operation pays a network round trip. A distributed file system or object storage is then preferable.
- Databases cope poorly with NFS's network locking and consistency guarantees: for a DBMS, prefer block storage (iSCSI or a local disk).
The right reflex: NFS for file sharing between trusted machines, a dedicated tool as soon as resilience, latency on small files or a database come into play.
Put it into practice
Mounting an NFS export by hand is one thing; guaranteeing it comes back after a reboot is another. This lab has you mount a share from a server and make the mount persistent via /etc/fstab, with a check after a reboot.
Key points
- NFS shares directories between Linux machines over the network, transparently for applications
- NFSv4.2 is the current version: a single port (2049/tcp), automatic negotiation, native Kerberos security
- The
/etc/exportsfile on the server controls what is shared, with whom and with which permissions - The
fsid=0option designates the root of the NFSv4 pseudo-filesystem, clients mount paths relative to that root - Use
syncin production (reliability),root_squash(security) and above allnofailin fstab, the only option that really protects the boot (_netdevhas no effect fornfs) - For performance, raise
rsize/wsizeto 1 MB and enablenconnect=4on fast networks - The default
sec=sysmode does not check UIDs: without Kerberos (sec=krb5p), NFS is only safe on an isolated, trusted network - Keep
hard(the default);softcan corrupt data. A process blocked on ahardmount stays killable withkill -9since kernel 2.6.25, the NFS wait beingTASK_KILLABLEdespite theDstate shown - The server decides, not the client's options: on an export declared
ro, writing fails withRead-only file systemeven if the mount succeeded - NFS shines at RWX as a Kubernetes backend, but remains a SPOF: for resilience or a database, look at CephFS or block storage
Knowledge check
Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: SMB/CIFS: network file sharing with Samba
A new full quiz with random questions
Work again only on the questions you missed