| 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:
export NFS_SERVER=vm2.lab.example
export NFS_CLIENT=vm1.lab.example
export NFS_EXPORT=/nfs/projects
export MOUNT_POINT=/mnt/nfs-projectsServer 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):
sudo dnf install -y nfs-utils
sudo systemctl enable --now nfs-server
sudo exportfs -rav
sudo exportfs -vClient host ($NFS_CLIENT):
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):
vm2.lab.example:/nfs/projects /mnt/nfs-projects nfs defaults,vers=4.2 0 0Prepare the NFS Server
SSH to the NFS server ($NFS_SERVER) and install the server and client utilities in one package:
sudo dnf install -y nfs-utilsComplete!Confirm the package version for your support notes:
rpm -q nfs-utilsnfs-utils-2.8.3-5.el10_2.x86_64Pick an export path that is not already used for local-only data. This lab uses /nfs/projects:
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:
sudo groupadd -g 1010 projects 2>/dev/null || true
sudo useradd -u 1010 -g projects -m nfsdemo 2>/dev/null || trueSet ownership and mode on the export directory. Red Hat’s NFS examples often use a group-writable directory with root_squash still enabled:
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:
sudo systemctl enable --now nfs-serverCreated 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:
systemctl is-active nfs-serveractiveactive 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:
/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:
/nfs/projects vm1.lab.example(rw,sync)Incorrect (space before options):
/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:
printf '%s %s(rw,sync)\n' "$NFS_EXPORT" "$NFS_CLIENT" | sudo tee -a /etc/exportsVerify the file:
grep -v '^#' /etc/exports | grep -v '^$'/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:
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:
sudo exportfs -v/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:
sudo firewall-cmd --permanent --add-service=nfssuccessApply permanent rules to the running firewall:
sudo firewall-cmd --reloadsuccessConfirm nfs is in the active service list:
firewall-cmd --list-services | tr ' ' '\n' | grep '^nfs$'nfsNFSv4 data traffic uses TCP port 2049. You can confirm the listener on the server:
ss -tlnp | grep 2049LISTEN 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:
ping -c 2 "$NFS_SERVER"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 msA 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:
sudo dnf install -y nfs-utilsrpm -q nfs-utils on the client should show the same package family as on the server.
Confirm the client resolves the server hostname:
getent hosts "$NFS_SERVER"192.168.56.117 vm2.lab.example vm2The 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:
sudo groupadd -g 1010 projects 2>/dev/null || true
sudo useradd -u 1010 -g projects -m nfsdemo 2>/dev/null || trueCreate an empty mount point:
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:
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:
findmnt "$MOUNT_POINT"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.117vm2.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:
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:
runuser -u nfsdemo -- cat "$MOUNT_POINT/lab-write-test.txt"nfs-lab-testOn the server, the file appears with matching UID and group:
ls -l "$NFS_EXPORT/lab-write-test.txt"-rw-r--r--. 1 nfsdemo projects 13 Aug 8 17:09 /nfs/projects/lab-write-test.txtOwnership on the server disk should show the same user and group names:
stat -c '%U %G %a %n' "$NFS_EXPORT/lab-write-test.txt"nfsdemo projects 644 /nfs/projects/lab-write-test.txtnfsdemo 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:
sudo mkdir -p /mnt/nfs-ro-testMount the same export read-only on the temporary path:
sudo mount -o vers=4.2,ro "$NFS_SERVER:$NFS_EXPORT" /mnt/nfs-ro-testCheck that ro appears in the active mount options:
findmnt /mnt/nfs-ro-testTARGET 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:
sudo umount /mnt/nfs-ro-testFor 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:
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/fstabYou 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:
tail -3 /etc/fstabUUID=78531da6-a389-46e8-9bad-267572940856 none swap defaults 0 0
vm2.lab.example:/nfs/projects /mnt/nfs-projects nfs defaults,vers=4.2 0 0The 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:
sudo umount "$MOUNT_POINT"Confirm the mount point is free before testing fstab:
findmnt "$MOUNT_POINT" 2>&1 || echo "not mounted"not mountedmount -a reads fstab and mounts every entry that is not already mounted:
sudo mount -aThe share should return with the same source and vers=4.2 options:
findmnt "$MOUNT_POINT"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:
id nobodyuid=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:
sudo touch "$MOUNT_POINT/root-squash-test.txt" 2>&1 || echo "root write denied"touch: cannot touch '/mnt/nfs-projects/root-squash-test.txt': Permission denied
root write deniedRemote 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:
ls -l /nfs/projects-lab/nrs-root.txt-rw-r--r--. 1 root root 9 Aug 8 17:09 /nfs/projects-lab/nrs-root.txtRemove 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:
sudo umount "$MOUNT_POINT"umount exits silently on success.
If you see target is busy, find processes still using the mount:
sudo findmnt "$MOUNT_POINT" && sudo lsof +f -- "$MOUNT_POINT" 2>/dev/null | head -5Stop 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)
- Install packages; create group
projects(GID 1010) and usernfsdemo(UID 1010). mkdir /nfs/projects;chown root:projects;chmod 2770.- Add
/nfs/projects vm1.lab.example(rw,sync)to/etc/exports— no space before(. sudo systemctl enable --now nfs-serversudo exportfs -rathensudo exportfs -v— confirmroot_squashin the live table.sudo firewall-cmd --permanent --add-service=nfs && sudo firewall-cmd --reload
On the NFS client (vm1.lab.example)
sudo dnf install -y nfs-utils- Create matching
projects/nfsdemowith the same UID and GID as on the server. getent hosts vm2.lab.example— must resolve the server IP.sudo mount -t nfs -o vers=4.2 vm2.lab.example:/nfs/projects /mnt/nfs-projectsrunuser -u nfsdemo -- teea test file;sudo touchon the mount should fail with defaultroot_squash.- Add
vm2.lab.example:/nfs/projects /mnt/nfs-projects nfs defaults,vers=4.2 0 0to/etc/fstab sudo umount /mnt/nfs-projects, thensudo mount -ato validate fstab- 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
- exports(5) — Linux manual page
- nfs(5) — Linux manual page
- mount.nfs(8) — Linux manual page
- Red Hat — Setting up an NFS server
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.

