Configure an NFS Server and Client in Linux

Tested on RHEL 10.2 (Coughlan) — vm2.lab.example (NFS server), vm1.lab.example (NFS client)
Package nfs-utils 2.8.3-5.el10_2
Applies to RHEL, Rocky Linux, AlmaLinux, Oracle Linux, CentOS Stream, Fedora
Privilege sudo or root on server and client
Scope NFS server install, /etc/exports, exportfs, firewalld for NFSv4, client mount and umount, vers and mount options, persistent fstab mounts, UID/GID ownership with root_squash, brief NFSv4 export paths, and troubleshooting. Does not cover Kerberos NFS, autofs maps, or a full export-options catalog.
Related guides Mount NFS share in Linux
NFS exports options examples
Mount with UUID and LABEL in fstab
Create and mount filesystems
RHCSA tutorial

NFS lets one Linux host share a directory tree and another host mount it like local storage. You need two roles: an NFS server that exports a path and an NFS client that mounts server:/export at a mount point. This walkthrough uses NFSv4 on RHEL-family systems with nfs-utils, with server commands on vm2.lab.example and client commands on vm1.lab.example. Trimmed outputs below come from that two-host lab on RHEL 10.2.

Lab host Role Typical use in this article
vm2.lab.example (192.168.56.117) NFS server Install nfs-utils, create /nfs/projects, edit /etc/exports, run nfs-server
vm1.lab.example (192.168.56.116) NFS client mount, /etc/fstab, read/write tests

Set hostnames once so every command block stays copy-paste friendly:

bash
export NFS_SERVER=vm2.lab.example
export NFS_CLIENT=vm1.lab.example
export NFS_EXPORT=/nfs/projects
export MOUNT_POINT=/mnt/nfs-projects

Server sections run on the NFS server host ($NFS_SERVER). Client sections run on the NFS client host ($NFS_CLIENT).

On Debian and Ubuntu, install nfs-kernel-server on the server and nfs-common on the client, use the distribution’s NFS service unit, and configure ufw or nftables instead of firewall-cmd. /etc/exports syntax and client mount commands are the same; the detailed command sequence below follows RHEL 10 with dnf, nfs-server, and firewalld.


How NFS Works

NFS (Network File System) is a remote filesystem protocol. The server owns directories on local disk; clients attach those trees at a mount point and use normal file tools (cp, editors, ls) across the network.

Term Meaning
NFS server Host that owns the exported directory and runs nfs-server
Export A server path registered in /etc/exports and published to clients
NFS client Host that runs mount and accesses files through a mount point
Remote filesystem The server export viewed from the client after mount

Access is UID/GID based, not username based. NFS checks numeric user and group IDs on the server when a client opens or creates a file. The same login name on two hosts does not guarantee the same UID. Export options such as root_squash change how remote root is mapped on the server.

NFSv3 uses multiple RPC services (rpcbind, mountd, nfs on port 2049) and separate mount protocol traffic. NFSv4 combines most work over NFS on port 2049 with a single server root (the pseudofilesystem). Modern RHEL and Fedora default clients to NFSv4. Use vers=3 only when a legacy system requires it and adjust the firewall accordingly.


NFS Quick Reference

Server host ($NFS_SERVER):

bash
sudo dnf install -y nfs-utils
sudo systemctl enable --now nfs-server
sudo exportfs -rav
sudo exportfs -v

Client host ($NFS_CLIENT):

bash
sudo dnf install -y nfs-utils
sudo mount -t nfs -o vers=4.2 "$NFS_SERVER:$NFS_EXPORT" "$MOUNT_POINT"
findmnt "$MOUNT_POINT"
sudo umount "$MOUNT_POINT"

Persistent client mount in /etc/fstab (field details in fstab UUID and LABEL guide):

text
vm2.lab.example:/nfs/projects /mnt/nfs-projects nfs defaults,vers=4.2 0 0

Prepare the NFS Server

SSH to the NFS server ($NFS_SERVER) and install the server and client utilities in one package:

bash
sudo dnf install -y nfs-utils
output
Complete!

Confirm the package version for your support notes:

bash
rpm -q nfs-utils
output
nfs-utils-2.8.3-5.el10_2.x86_64

Pick an export path that is not already used for local-only data. This lab uses /nfs/projects:

bash
sudo mkdir -p "$NFS_EXPORT"

Create a shared group and a lab user on the server so the client can write without disabling root_squash:

bash
sudo groupadd -g 1010 projects 2>/dev/null || true
sudo useradd -u 1010 -g projects -m nfsdemo 2>/dev/null || true

Set ownership and mode on the export directory. Red Hat’s NFS examples often use a group-writable directory with root_squash still enabled:

bash
sudo chown root:projects "$NFS_EXPORT"
sudo chmod 2770 "$NFS_EXPORT"

2770 gives the projects group read/write access, and the setgid bit causes newly created files and directories to inherit the projects group.

Start and enable the NFS server unit so exports survive reboot:

bash
sudo systemctl enable --now nfs-server
output
Created symlink '/etc/systemd/system/multi-user.target.wants/nfs-server.service' → '/usr/lib/systemd/system/nfs-server.service'.

Confirm the unit is running before you edit exports:

bash
systemctl is-active nfs-server
output
active

active means the daemon is running; you still need /etc/exports entries before clients see data.


Configure /etc/exports

Each line in /etc/exports declares one exported path and which clients may mount it. The general shape is:

text
/path client(options)
Client specifier Example Meaning
Hostname vm1.lab.example Only that host
IP address 192.168.56.116 Only that address
Subnet 192.168.56.0/24 Any address in the subnet
All hosts * World export — avoid on production networks

Common options for a writable project share:

Option Effect
rw Read/write (default is often ro if unspecified)
ro Read-only
sync Commit writes before replying (recommended; avoids exportfs warnings)
root_squash Map client root to nobody on the server (default when not overridden)
no_root_squash Allow client root to act as root on the export — lab comparison only

Whitespace matters. Options must sit in parentheses immediately after the client specifier with no space between the host and (. Red Hat documents that a space before (options) changes how the line is parsed and can produce unexpected export behavior.

Correct:

text
/nfs/projects vm1.lab.example(rw,sync)

Incorrect (space before options):

text
/nfs/projects vm1.lab.example (rw,sync)

Add the export line on the server. Restrict the client to $NFS_CLIENT and rely on default root_squash:

bash
printf '%s %s(rw,sync)\n' "$NFS_EXPORT" "$NFS_CLIENT" | sudo tee -a /etc/exports

Verify the file:

bash
grep -v '^#' /etc/exports | grep -v '^$'
output
/nfs/projects vm1.lab.example(rw,sync)

For a longer options list (all_squash, subnet exports, multiple clients), see NFS exports options examples.

Comparison only: no_root_squash lets client root create root:root files on the server. Export a separate path with mode 1777 and (rw,sync,no_root_squash) when you need to demonstrate that behavior in a lab — do not use it as the default production pattern.


Reload and Inspect NFS Exports

/etc/exports is the configuration file. The running export table is what nfs-server and exportfs apply in memory. After every edit, reload exports on the server:

bash
sudo exportfs -ra

-r re-reads /etc/exports. -a applies all entries. There is no output on success.

List what the server is actually exporting and the options in effect:

bash
sudo exportfs -v
output
/nfs/projects 	vm1.lab.example(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,root_squash,no_all_squash)

root_squash in the live table confirms client root is mapped to the anonymous account. exportfs -v on the server is the authoritative verification step for NFSv4 labs that open only the nfs firewalld service.


Configure firewalld for NFS

On the NFS server, allow NFS through firewalld. For a straightforward NFSv4 lab, the predefined nfs service is enough:

bash
sudo firewall-cmd --permanent --add-service=nfs
output
success

Apply permanent rules to the running firewall:

bash
sudo firewall-cmd --reload
output
success

Confirm nfs is in the active service list:

bash
firewall-cmd --list-services | tr ' ' '\n' | grep '^nfs$'
output
nfs

NFSv4 data traffic uses TCP port 2049. You can confirm the listener on the server:

bash
ss -tlnp | grep 2049
output
LISTEN 0      4096         0.0.0.0:2049       0.0.0.0:*
LISTEN 0      4096            [::]:2049          [::]:*

NFSv3 additionally needs RPC-related services (rpc-bind, mountd, and sometimes nfs RPC helpers). Open nfs, mountd, and rpc-bind permanent services on the server, or use firewall-cmd --permanent --add-service=nfs3 where your distribution provides it. Open those same RPC services if you specifically need remote showmount or NFSv3 client discovery across the firewall.

Test from the client after firewall changes:

bash
ping -c 2 "$NFS_SERVER"
output
PING vm2.lab.example (192.168.56.117) 56(84) bytes of data.
64 bytes from vm2.lab.example (192.168.56.117): icmp_seq=1 ttl=64 time=1.12 ms
64 bytes from vm2.lab.example (192.168.56.117): icmp_seq=2 ttl=64 time=1.16 ms

--- vm2.lab.example ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 1.118/1.138/1.159/0.020 ms

A reply confirms basic IP reachability; NFS still needs the export, correct options, and matching vers= on mount.


Prepare the NFS Client

SSH to the NFS client ($NFS_CLIENT) and install client tools:

bash
sudo dnf install -y nfs-utils

rpm -q nfs-utils on the client should show the same package family as on the server.

Confirm the client resolves the server hostname:

bash
getent hosts "$NFS_SERVER"
output
192.168.56.117  vm2.lab.example vm2

The address must be the server, not the client. Fix DNS or add a static /etc/hosts line on the client if lookup fails.

Create the same nfsdemo user and projects group on the client with matching UID and GID so NFS ownership lines up:

bash
sudo groupadd -g 1010 projects 2>/dev/null || true
sudo useradd -u 1010 -g projects -m nfsdemo 2>/dev/null || true

Create an empty mount point:

bash
sudo mkdir -p "$MOUNT_POINT"

Discover Available NFS Exports

For NFSv4, mount the export path you configured on the server. You do not need a discovery command when you already know /nfs/projects from /etc/exports or your runbook.

On the server, exportfs -v confirms the live export table before you switch to the client. That output plus a successful client mount are the primary validation steps for this lab.

Optional diagnostic: showmount -e queries rpc.mountd, not the NFSv4 protocol itself. With only the nfs firewalld service and TCP 2049 open, NFSv4 mounts often work while remote showmount fails or times out. Open rpc-bind and mountd on the server firewall only when you specifically need remote showmount or NFSv3-style discovery. showmount -e is not a reliable authorization test for whether your client may mount — match the client hostname or address in /etc/exports and verify with exportfs -v on the server.


Mount an NFS Share

Mount the export on the client with NFSv4.2 explicitly so the client does not fall back to an older version silently:

bash
sudo mount -t nfs -o vers=4.2 "$NFS_SERVER:$NFS_EXPORT" "$MOUNT_POINT"

mount prints nothing when the mount succeeds.

Confirm the mount with findmnt, which shows source, type, and options:

bash
findmnt "$MOUNT_POINT"
output
TARGET            SOURCE                        FSTYPE OPTIONS
/mnt/nfs-projects vm2.lab.example:/nfs/projects nfs4   rw,relatime,vers=4.2,rsize=1048576,wsize=1048576,namlen=255,hard,fatal_neterrors=none,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=192.168.56.116,local_lock=none,addr=192.168.56.117

vm2.lab.example:/nfs/projects in the SOURCE column and vers=4.2 in OPTIONS confirm the client mounted the server export over NFSv4.2.

Write a test file as the lab user through the mount:

bash
printf 'nfs-lab-test\n' | runuser -u nfsdemo -- tee "$MOUNT_POINT/lab-write-test.txt"

Read the file back through the NFS mount to confirm client write access:

bash
runuser -u nfsdemo -- cat "$MOUNT_POINT/lab-write-test.txt"
output
nfs-lab-test

On the server, the file appears with matching UID and group:

bash
ls -l "$NFS_EXPORT/lab-write-test.txt"
output
-rw-r--r--. 1 nfsdemo projects 13 Aug  8 17:09 /nfs/projects/lab-write-test.txt

Ownership on the server disk should show the same user and group names:

bash
stat -c '%U %G %a %n' "$NFS_EXPORT/lab-write-test.txt"
output
nfsdemo projects 644 /nfs/projects/lab-write-test.txt

nfsdemo and projects on the server match the client user because UIDs and GIDs align and the directory grants group write access.


Specify an NFS Version and Mount Options

Pass mount options with -o on a one-time mount or in the fourth field of /etc/fstab.

Option Use
vers=4.2 Prefer modern NFSv4.2
vers=3 Legacy NFSv3 clients or servers
ro Read-only mount on the client
rw Read/write (default for writable exports)
hard Retry I/O until the server returns (default on RHEL)
timeo=600 RPC timeout in deciseconds before retry
_netdev Explicitly mark a fstab entry as network-dependent; normally redundant for nfs because systemd already orders NFS mounts after the network

Read-only test mount on a second mount point:

bash
sudo mkdir -p /mnt/nfs-ro-test

Mount the same export read-only on the temporary path:

bash
sudo mount -o vers=4.2,ro "$NFS_SERVER:$NFS_EXPORT" /mnt/nfs-ro-test

Check that ro appears in the active mount options:

bash
findmnt /mnt/nfs-ro-test
output
TARGET           SOURCE                        FSTYPE OPTIONS
/mnt/nfs-ro-test vm2.lab.example:/nfs/projects nfs4   ro,relatime,vers=4.2,...

ro in the options column confirms the read-only mount. Unmount the test path when finished:

bash
sudo umount /mnt/nfs-ro-test

For a wider mount-option catalog (bg, soft, nolock, performance tuning), see mount NFS share in Linux.


Configure a Persistent NFS Mount

Add one line to /etc/fstab on the client so the share mounts at boot. RHEL 10’s NFS fstab example uses defaults without _netdev because systemd already recognizes nfs as a network filesystem and orders the mount after network-online.target:

bash
FSTAB_LINE="$NFS_SERVER:$NFS_EXPORT $MOUNT_POINT nfs defaults,vers=4.2 0 0"
grep -q "$MOUNT_POINT" /etc/fstab || echo "$FSTAB_LINE" | sudo tee -a /etc/fstab

You may add _netdev (defaults,_netdev,vers=4.2) when you want the network dependency spelled out in the file; it is normally redundant for nfs on systemd.

The new line should appear at the end of the file:

bash
tail -3 /etc/fstab
output
UUID=78531da6-a389-46e8-9bad-267572940856 none                    swap    defaults        0 0
vm2.lab.example:/nfs/projects /mnt/nfs-projects nfs defaults,vers=4.2 0 0

The fifth and sixth fstab fields (0 0 here) mean skip dump and skip fsck for this network filesystem. See mount with UUID and LABEL in fstab for field-by-field detail.

Unmount the temporary mount and validate the fstab entry:

bash
sudo umount "$MOUNT_POINT"

Confirm the mount point is free before testing fstab:

bash
findmnt "$MOUNT_POINT" 2>&1 || echo "not mounted"
output
not mounted

mount -a reads fstab and mounts every entry that is not already mounted:

bash
sudo mount -a

The share should return with the same source and vers=4.2 options:

bash
findmnt "$MOUNT_POINT"
output
TARGET            SOURCE                        FSTYPE OPTIONS
/mnt/nfs-projects vm2.lab.example:/nfs/projects nfs4   rw,relatime,vers=4.2,...

mount -a succeeded and the share is back without a reboot. On RHEL 10 also run sudo systemctl daemon-reload after fstab edits so systemd regenerates .mount units.

Reboot the client when you can spare a maintenance window and run findmnt "$MOUNT_POINT" again to confirm the fstab line survives boot. Skip reboot on disposable lab VMs if you already validated with mount -a.


Understand NFS File Ownership

NFS enforces POSIX permissions on the server export directory. The client sees the same numeric UID, GID, and mode bits the server stores.

Check the nobody account used when root_squash maps client root:

bash
id nobody
output
uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)

With default root_squash and mode 2770 on root:projects, client root cannot write to the share. Run the test as root on the client with sudo so the failure reflects root_squash, not the shell user’s UID:

bash
sudo touch "$MOUNT_POINT/root-squash-test.txt" 2>&1 || echo "root write denied"
output
touch: cannot touch '/mnt/nfs-projects/root-squash-test.txt': Permission denied
root write denied

Remote UID 0 is mapped to the anonymous user by default, while the directory is root:projects with mode 2770, so the squashed user has no write permission.

If user bob is UID 1000 on the client but UID 1001 on the server, files created as bob on the mount will not match bob on the server disk. Align UIDs with LDAP, /etc/passwd sync, or dedicated service accounts instead of chmod 777, which exposes the export to every user on every allowed client.

no_root_squash comparison (lab only): export a separate path such as /nfs/projects-lab with (rw,sync,no_root_squash) and mode 1777. Client root can then create server-side root:root files:

bash
ls -l /nfs/projects-lab/nrs-root.txt
output
-rw-r--r--. 1 root root 9 Aug  8 17:09 /nfs/projects-lab/nrs-root.txt

Remove that export after the comparison; production shares should keep root_squash and fix ownership with groups or matching UIDs.


Understand NFSv4 Pseudofilesystem Basics

In this lab /nfs/projects is exported directly, so the client mounts vm2.lab.example:/nfs/projects. That path is both the directory on the server disk and the path clients use in the mount command.

More advanced servers can define an NFSv4 pseudo-root with fsid=0 in /etc/exports, which builds a virtual hierarchy so client-visible paths (for example /projects) may differ from the physical directory layout on disk. That arrangement is optional and not used in this walkthrough. If a path works on the server disk but fails from the client, compare the /etc/exports path with the client mount source and exportfs -v.


Unmount an NFS Filesystem

Unmount from the client when maintenance or fstab edits require it:

bash
sudo umount "$MOUNT_POINT"

umount exits silently on success.

If you see target is busy, find processes still using the mount:

bash
sudo findmnt "$MOUNT_POINT" && sudo lsof +f -- "$MOUNT_POINT" 2>/dev/null | head -5

Stop or exit those processes, cd out of the mount point, then retry umount. Avoid umount -l (lazy) or umount -f (force) in production unless you understand stale mount cleanup; fix the busy reference instead.


Troubleshoot NFS

Symptom Likely cause Fix
Export not visible from client Client host not listed in /etc/exports; stale export table Fix the client specifier; sudo exportfs -ra on server; verify with exportfs -v
access denied by server Client IP or hostname mismatch; wrong export path Match client identity in exports; mount exact path from exportfs -v
Connection timeout Server down, wrong IP, routing, or firewall ping server; open nfs service on server firewalld; check ss -tlnp | grep 2049
Firewall works locally but not remotely firewall-cmd change without --permanent or no reload --permanent --add-service=nfs; firewall-cmd --reload
showmount fails but mount works Only TCP 2049 open; mountd not reachable Expected for NFSv4-only firewall; mount known path; open rpc-bind and mountd if you need showmount
Permission denied after mount Export ro; directory mode; root_squash Adjust export options; fix ownership on server path; align UIDs; do not use chmod 777
Wrong owner on new files UID/GID mismatch between hosts Align IDs or use all_squash with anonuid/anongid intentionally
Stale file handle Server export removed or renamed while client mounted umount client; fix server export; remount
Slow boot with NFS fstab line Server unavailable at boot Fix server reachability; consider nofail only when appropriate; systemd already orders nfs mounts after the network
Server works locally but not remotely Export lists only localhost; SELinux or firewall Export to client hostname or subnet; check nfs service in firewalld

Complete Two-System NFS Example

The lists below split server and client work. Run each block on the matching host.

On the NFS server (vm2.lab.example)

  1. Install packages; create group projects (GID 1010) and user nfsdemo (UID 1010).
  2. mkdir /nfs/projects; chown root:projects; chmod 2770.
  3. Add /nfs/projects vm1.lab.example(rw,sync) to /etc/exports — no space before (.
  4. sudo systemctl enable --now nfs-server
  5. sudo exportfs -ra then sudo exportfs -v — confirm root_squash in the live table.
  6. sudo firewall-cmd --permanent --add-service=nfs && sudo firewall-cmd --reload

On the NFS client (vm1.lab.example)

  1. sudo dnf install -y nfs-utils
  2. Create matching projects / nfsdemo with the same UID and GID as on the server.
  3. getent hosts vm2.lab.example — must resolve the server IP.
  4. sudo mount -t nfs -o vers=4.2 vm2.lab.example:/nfs/projects /mnt/nfs-projects
  5. runuser -u nfsdemo -- tee a test file; sudo touch on the mount should fail with default root_squash.
  6. Add vm2.lab.example:/nfs/projects /mnt/nfs-projects nfs defaults,vers=4.2 0 0 to /etc/fstab
  7. sudo umount /mnt/nfs-projects, then sudo mount -a to validate fstab
  8. Reboot the client when possible and confirm the mount returns.

For on-demand NFS mounts instead of fstab, use autofs maps on your distribution (not covered here).


References


Summary

You configured NFS end to end on two hosts: the server on vm2.lab.example exports /nfs/projects with root_squash and group-writable permissions, exportfs -v confirms the live table, and firewalld allows the nfs service for NFSv4. On vm1.lab.example you mounted vm2.lab.example:/nfs/projects with vers=4.2, verified with findmnt, and persisted the mount in /etc/fstab with defaults,vers=4.2.

The detail that breaks most lab setups is /etc/exports syntax — options must follow the client name with no space before (. For NFSv4, mount the known export path instead of depending on showmount, which talks to mountd and may fail when only TCP 2049 is open. Permission problems after a successful mount are usually export options, root_squash, or UID/GID mismatch — not a broken mount command.

For mount-option depth and client-only workflows, continue with mount NFS share in Linux. For export option catalogs and multi-client lines, use NFS exports options examples. For fstab field semantics beyond the NFS line, see fstab UUID and LABEL.


Frequently Asked Questions

1. Should I use NFSv3 or NFSv4 on RHEL 10?

NFSv4 is the default on current RHEL and Fedora releases. It uses fewer RPC services and one TCP port for the data path. Use NFSv3 only when a legacy client or appliance requires it and open the extra RPC and mountd services in the firewall.

2. Why does showmount fail on an NFSv4 server?

showmount queries rpc.mountd, not the NFSv4 protocol itself. With only the nfs firewalld service and TCP 2049 open, NFSv4 mounts often work while remote showmount fails. Use exportfs -v on the server and mount the known export path from the client.

3. Why add _netdev to an NFS fstab line?

_netdev explicitly marks an fstab entry as network-dependent. For nfs filesystem type, systemd already treats the mount as remote and orders it after network-online.target, so _netdev is normally redundant. You may still add it for clarity alongside defaults and vers=4.2.

4. What does root_squash do on an NFS export?

root_squash maps remote root on the client to the anonymous nobody user on the server, so root on a client cannot create root-owned files on the export. no_root_squash disables that mapping and is appropriate only for tightly controlled lab hosts.

5. Why do I get Permission denied after a successful NFS mount?

The mount succeeded but the server export options or local POSIX permissions on the export directory block the operation. Check export options in /etc/exports, the mode and owner on the server path, and whether root_squash applies. UID and GID numbers must align across hosts for user-owned files.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)