Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Use NFS on Linux

By updated

Improve this page

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-server and nfs-common on Debian and Ubuntu, nfs-utils on Rocky, RHEL and AlmaLinux
  • Export configuration: /etc/exports, rw, ro, sync options
  • Client-side mounting: mount command, /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

VersionYearMain contributions
NFSv21989First public version, files limited to 2 GB
NFSv31995Large files, asynchronous writes, better cache
NFSv42003Stateful protocol, a single port (2049/tcp), native Kerberos security
NFSv4.12010Sessions, pNFS (parallel NFS) for performance
NFSv4.22016Server-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.

CriterionNFSiSCSISamba (CIFS)SSHFS
ProtocolFilesBlockFilesFiles
Port2049/tcp3260/tcp445/tcp22/tcp
Native clientsLinux, macOSLinux, WindowsWindows, LinuxLinux, macOS
PerformanceGoodExcellentAverageLow
ComplexityLowModerateLowVery low
Use caseLinux sharesVirtual disksWindows sharesOccasional access

Prerequisites

  • 2 Ubuntu 24.04 machines (physical or virtual)
  • Network connectivity between the two (same subnet or routed)
  • root or sudo access 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.

Terminal window
sudo apt update
sudo apt install nfs-kernel-server

The service starts automatically. Check its state:

Terminal window
sudo systemctl status nfs-server

Expected 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:

Terminal window
sudo mkdir -p /srv/nfs/partage /srv/nfs/projets
sudo chown 65534:65534 /srv/nfs/partage /srv/nfs/projets

The 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:

Terminal window
sudo nano /etc/exports
/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

OptionMeaningWhen to use it
rwRead and write allowedCollaborative shares
roRead onlyConfig distribution, documentation
syncWrites to disk before answering the clientAlways in production (reliability)
asyncAnswers before writing to diskPerformance tests only (risk of loss)
fsid=0Designates the root of the NFSv4 pseudo-filesystemA single line with this option
crossmntCrosses sub-mounts automaticallyOn the export root
no_subtree_checkDisables subtree checkingRecommended (performance + stability)
no_root_squashThe client's root stays root on the serverAutomated deployment, Ansible
root_squashThe client's root becomes nobody (default)Production (security)
all_squashAll users become nobodyPublic read-only shares
anonuid=N / anongid=NUID/GID used for "squashed" usersControl the permissions of nobody

Apply the exports

After any change to /etc/exports, apply the changes:

Terminal window
sudo exportfs -ra

Check: show the active exports with their options.

Terminal window
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.

Terminal window
sudo apt update
sudo apt install nfs-common

Manual mount

Create the mount points and mount the shares:

Terminal window
sudo mkdir -p /mnt/nfs-partage /mnt/nfs-projets
sudo mount -t nfs4 192.168.122.50:/partage /mnt/nfs-partage
sudo mount -t nfs4 192.168.122.50:/projets /mnt/nfs-projets

Check: confirm the mount and the negotiated version.

Terminal window
mount | grep nfs4
192.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:

Terminal window
# On the client: write to the rw share
echo "Données partagées via NFS" | sudo tee /mnt/nfs-partage/test.txt
# Check reading
cat /mnt/nfs-partage/test.txt
Données partagées via NFS

Check that the read-only share does refuse writing:

Terminal window
echo "test" | sudo tee /mnt/nfs-projets/interdit.txt
tee: /mnt/nfs-projets/interdit.txt: Read-only file system

The 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:

/etc/fstab
192.168.122.50:/partage /mnt/nfs-partage nfs4 defaults,_netdev,nofail 0 0
192.168.122.50:/projets /mnt/nfs-projets nfs4 defaults,ro,_netdev,nofail 0 0

It 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.

Terminal window
sudo mount -a
df -h | grep nfs

Useful mount options

OptionEffectRecommendation
vers=4.2Forces NFSv4.2Useful if the server supports several versions
hardRetries forever on an outage (default)Production (no data loss)
softGives up after a delayNon-critical batch scripts
noatimeDoes not update the last access datePerformance (fewer writes)
rsize=262144Read block size (bytes)Increase if bandwidth is available
wsize=262144Write block size (bytes)Same
nconnect=4Parallel TCP connectionsFast network (10 Gbps+)
_netdevFlags a network mountNo effect for nfs: systemd already knows
nofailBoot goes on if the server is missingIt 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.

Terminal window
# Check a user's UID
id bob
# uid=1000(bob) gid=1000(bob) groups=1000(bob)

If the UIDs differ, change them:

Terminal window
sudo usermod -u 1000 bob
sudo groupmod -g 1000 bob
sudo find / -user OLD_UID -exec chown -h 1000 {} \;

Optimise 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:

Terminal window
# Write (100 MB, direct I/O)
dd if=/dev/zero of=/mnt/nfs-partage/bench bs=1M count=100 oflag=direct
# Read
dd if=/mnt/nfs-partage/bench of=/dev/null bs=1M iflag=direct

On our lab (virtio network, same KVM host), the results are:

DirectionMeasured throughput
Write~135 MB/s
Read~2.7 GB/s

Mount tuning

For better throughput, increase the block size and enable parallel connections:

Terminal window
sudo mount -t nfs4 \
-o rsize=1048576,wsize=1048576,noatime,nconnect=4 \
192.168.122.50:/partage /mnt/nfs-partage

Equivalent in /etc/fstab:

192.168.122.50:/partage /mnt/nfs-partage nfs4 rsize=1048576,wsize=1048576,noatime,nconnect=4,_netdev,nofail 0 0

System tuning

Increase the kernel's network buffers to support large transfers:

/etc/sysctl.d/99-nfs-perf.conf
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
Terminal window
sudo sysctl -p /etc/sysctl.d/99-nfs-perf.conf

Monitoring

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.

CommandUse
nfsstat -sServer-side NFS statistics (RPC calls)
nfsstat -cClient-side NFS statistics
mountstatsPer-mount details (latency, retransmissions)
iostat -x 1Real-time disk activity
Terminal window
# See the detailed statistics of a mount
mountstats /mnt/nfs-partage

Secure 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.

LevelOptionAuthenticationIntegrityEncryptionPerformance impact
Basicsec=krb5YesNoNoLow
Integritysec=krb5iYesYesNoModerate
Fullsec=krb5pYesYesYesSignificant (~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.

  1. Install the Kerberos packages:

    Terminal window
    sudo apt install krb5-user nfs-kernel-server
  2. Create an NFS principal in the KDC:

    Terminal window
    kadmin -q "addprinc -randkey nfs/serveur.lab.local"
  3. 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
  4. Configure the export with Kerberos:

    /etc/exports
    /srv/nfs 192.168.122.0/24(rw,sync,fsid=0,crossmnt,sec=krb5p)
  5. Restart the server:

    Terminal window
    sudo exportfs -ra
    sudo systemctl restart nfs-server

Client-side configuration

  1. Install the packages:

    Terminal window
    sudo apt install krb5-user nfs-common
  2. Get a Kerberos ticket:

    Terminal window
    kinit utilisateur@LAB.LOCAL
    klist # check the ticket
  3. Mount with Kerberos:

    Terminal window
    sudo mount -t nfs4 -o sec=krb5p serveur.lab.local:/ /mnt/nfs

Check:

Terminal window
mount | grep nfs4 | grep sec=krb5p

If 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

SymptomLikely causeFix
mount hangs or times outNFS service stopped on the serversudo systemctl start nfs-server
mount.nfs4: access deniedClient IP missing from /etc/exportsCheck the network in /etc/exports and exportfs -ra
Permission denied on filesroot_squash active (root → nobody)Use no_root_squash or work without sudo
Files owned by nobodyDifferent idmapd domain on client/serverSynchronise /etc/idmapd.conf (same Domain)
Mounted as NFSv3 instead of v4Client negotiating a low versionForce mount -t nfs4 or vers=4.2 in fstab
Degraded performanceBlocks too small (rsize/wsize)Increase to 1048576 + enable nconnect=4
kinit failsClock out of sync (>5 min) vs KDCSynchronise with NTP: sudo systemctl restart chronyd
Port 2049 filteredFirewall blocks NFS trafficsudo ufw allow from 192.168.122.0/24 to any port 2049
Stale file handleExport changed without exportfs -rasudo 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/exports file on the server controls what is shared, with whom and with which permissions
  • The fsid=0 option designates the root of the NFSv4 pseudo-filesystem, clients mount paths relative to that root
  • Use sync in production (reliability), root_squash (security) and above all nofail in fstab, the only option that really protects the boot (_netdev has no effect for nfs)
  • For performance, raise rsize/wsize to 1 MB and enable nconnect=4 on fast networks
  • The default sec=sys mode does not check UIDs: without Kerberos (sec=krb5p), NFS is only safe on an isolated, trusted network
  • Keep hard (the default); soft can corrupt data. A process blocked on a hard mount stays killable with kill -9 since kernel 2.6.25, the NFS wait being TASK_KILLABLE despite the D state shown
  • The server decides, not the client's options: on an export declared ro, writing fails with Read-only file system even 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

6 questions
6 min
70% to pass

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

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn