NFS stands for Network File System.
It is a protocol that allows one computer to share directories with other computers over a network.
The important idea is:
A remote directory can appear like a local directory.
For example, a server may export:
/opt/shared
A client may mount it at:
/mnt/nfs_shared
Then users on the client can access remote files as if they were local:
ls /mnt/nfs_shared
NFS has two main sides:
Diagram:
Client Network Server
+--------+ +--------+
| User | file open/read/write requests | NFS |
| Space | <-------------------------------> | Server |
+--------+ +--------+
| |
+--------+ +--------+
| NFS | | NFS |
| Client | | Daemon |
+--------+ +--------+
| |
+--------+ +--------+
| Mount | | Local |
| Point | | Export |
+--------+ +--------+
| |
+--------+ +--------+
| Apps | | Disk |
+--------+ +--------+
When a client reads or writes a file under the NFS mount point, the NFS client code sends network requests to the server.
The server receives those requests and performs file operations on its own local filesystem.
NFS is useful when several systems need shared access to files.
Common reasons include:
Example use cases:
NFS is common in Linux and Unix environments, but clients also exist for macOS and Windows.
The NFS server owns and exports the real directory.
The NFS client mounts that exported directory.
Diagram:
Server filesystem:
/
└── opt
└── shared
├── file1.txt
└── file2.txt
Client filesystem:
/
└── mnt
└── nfs_shared
├── file1.txt
└── file2.txt
The files are physically stored on the server, but visible through the client mount point.
Common NFS-related components include:
NFSv3 often depends on several RPC services and ports.
NFSv4 simplifies firewalling because it mainly uses TCP port 2049.
NFS has several versions.
NFSv2 is old and rarely used today.
Limitations include:
NFSv3 is still widely used.
It introduced improvements such as:
NFSv3 is stable and common, but it often requires more firewall considerations because supporting services may use multiple ports.
NFSv4 is newer and usually preferred for modern environments.
Advantages include:
For secure or enterprise environments, NFSv4 with Kerberos is often preferred.
The NFS server needs:
The basic flow is:
On RHEL, CentOS, Rocky, AlmaLinux, or Fedora-style systems:
sudo dnf install nfs-utils
On older CentOS 7 systems:
sudo yum install nfs-utils
On Debian or Ubuntu systems:
sudo apt install nfs-kernel-server nfs-common
Package names vary slightly by distribution, but the main idea is the same:
Example server directory:
sudo mkdir -p /opt/shared
Add a test file:
echo "hello from NFS server" | sudo tee /opt/shared/hello.txt
Set ownership and permissions based on your use case.
For a simple lab:
sudo chmod 755 /opt/shared
For a shared writable directory, you may use a group:
sudo groupadd nfsusers
sudo chgrp nfsusers /opt/shared
sudo chmod 2775 /opt/shared
The 2 in 2775 sets the setgid bit so new files tend to inherit the directory group.
/etc/exportsThe server uses /etc/exports to define what directories are shared and who can access them.
Example:
/opt/shared 192.168.1.0/24(rw,sync,root_squash)
Meaning:
A more restrictive example:
/opt/shared 192.168.1.50(ro,sync,root_squash)
This allows only one client and gives read-only access.
A common safe default:
/opt/shared 192.168.1.0/24(rw,sync,root_squash,no_subtree_check)
Important warning:
After editing /etc/exports, apply changes with:
sudo exportfs -r
View exports:
sudo exportfs -v
Example output:
/opt/shared 192.168.1.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,root_squash,no_all_squash)
Interpretation:
On systemd systems:
sudo systemctl enable --now nfs-server
Check status:
systemctl status nfs-server
Example output:
● nfs-server.service - NFS server and services
Loaded: loaded
Active: active (exited)
Interpretation:
On older setups, you may also see or manage:
sudo systemctl enable --now rpcbind
sudo systemctl enable --now nfs-idmapd
For NFSv4, TCP port 2049 is the main port.
For NFSv3, additional RPC services such as mountd and rpcbind may be needed.
With firewalld:
sudo firewall-cmd --permanent --add-service=nfs
sudo firewall-cmd --permanent --add-service=mountd
sudo firewall-cmd --permanent --add-service=rpc-bind
sudo firewall-cmd --reload
Check:
sudo firewall-cmd --list-services
Example output:
ssh dhcpv6-client nfs mountd rpc-bind
Interpretation:
With UFW, a simple NFSv4 example:
sudo ufw allow from 192.168.1.0/24 to any port 2049 proto tcp
The NFS client needs:
Install packages.
RHEL-style:
sudo dnf install nfs-utils
Debian/Ubuntu:
sudo apt install nfs-common
Create mount point:
sudo mkdir -p /mnt/nfs_shared
Mount:
sudo mount -t nfs 192.168.1.100:/opt/shared /mnt/nfs_shared
For NFSv4 explicitly:
sudo mount -t nfs4 192.168.1.100:/opt/shared /mnt/nfs_shared
Check:
mount | grep nfs
Example output:
192.168.1.100:/opt/shared on /mnt/nfs_shared type nfs4 (rw,relatime,vers=4.2,addr=192.168.1.100)
Interpretation:
List files:
ls -l /mnt/nfs_shared
Example output:
-rw-r--r-- 1 root root 22 Jun 1 12:00 hello.txt
Read the test file:
cat /mnt/nfs_shared/hello.txt
Example output:
hello from NFS server
Create a file if write access is allowed:
touch /mnt/nfs_shared/client-test.txt
If this works, the mount is writable for your user.
If it fails with permission denied, check Unix permissions, UID/GID mapping, root squashing, and export options.
/etc/fstabTo mount automatically at boot, add an entry on the client.
Example:
192.168.1.100:/opt/shared /mnt/nfs_shared nfs defaults,_netdev 0 0
For NFSv4:
192.168.1.100:/opt/shared /mnt/nfs_shared nfs4 defaults,_netdev 0 0
Important option:
_netdev means this mount depends on the network.
For systems where the NFS server may not always be available, consider:
192.168.1.100:/opt/shared /mnt/nfs_shared nfs4 defaults,_netdev,nofail,x-systemd.automount 0 0
Meaning:
Test fstab:
sudo mount -a
Check:
findmnt /mnt/nfs_shared
NFS permissions are based heavily on numeric user IDs and group IDs.
This is a major concept.
Linux file ownership is stored as numbers:
Example:
id alice
Output:
uid=1000(alice) gid=1000(alice) groups=1000(alice)
If Alice has UID 1000 on the client but UID 2000 on the server, permissions may not behave as expected.
Server thinks alice = UID 2000
NFS sends numeric UID 1000.
This can cause:
Common strategies include:
In small labs, manually matching UIDs may be enough.
In larger environments, use centralized identity management.
idmapdNFSv4 can use name-based identity mapping through idmapd.
The client and server should use the same domain in:
/etc/idmapd.conf
Example:
[General]
Domain = example.com
Restart relevant services after changes.
Example:
sudo systemctl restart nfs-idmapd
sudo systemctl restart nfs-server
Check identity mapping issues if files appear owned by:
These often indicate mapping problems.
Root squashing protects the server from root users on clients.
With root_squash, a request from UID 0 on the client is mapped to an anonymous user on the server.
Client root UID 0
|
v
NFS server maps it to anonymous user
|
v
Usually nfsnobody or nobody
This prevents client root from automatically having root privileges on the server export.
Example export:
/opt/shared 192.168.1.0/24(rw,sync,root_squash)
Dangerous option:
/opt/shared 192.168.1.0/24(rw,sync,no_root_squash)
Use no_root_squash only in carefully controlled environments.
all_squashThe all_squash option maps all client users to the anonymous user.
Example:
/opt/public 192.168.1.0/24(rw,sync,all_squash)
This can be useful for simple public drop-box style shares where all access should use one server-side identity.
Common related options:
Example:
/opt/public 192.168.1.0/24(rw,sync,all_squash,anonuid=2000,anongid=2000)
This maps all access to UID 2000 and GID 2000.
NFS is powerful, but it must be configured carefully.
Good practices:
A safe export is usually specific:
/srv/project 192.168.10.0/24(rw,sync,root_squash,no_subtree_check)
A risky export is broad:
/ *(rw,no_root_squash)
Avoid broad exports like that.
NFS performance depends on:
Common mount options include:
Example:
sudo mount -t nfs -o rsize=8192,wsize=8192 192.168.1.100:/opt/shared /mnt/nfs_shared
Modern systems often negotiate good defaults automatically. Tune only after measuring.
Important safety note:
exportfsView exports:
sudo exportfs -v
Reload exports:
sudo exportfs -r
Unexport one directory:
sudo exportfs -u 192.168.1.0/24:/opt/shared
Unexport all:
sudo exportfs -ua
Re-export all from /etc/exports:
sudo exportfs -a
Set up a server export and mount it from a client.
Install packages:
sudo dnf install nfs-utils
Create directory:
sudo mkdir -p /opt/shared
echo "hello from server" | sudo tee /opt/shared/hello.txt
sudo chmod 755 /opt/shared
Edit /etc/exports:
/opt/shared 192.168.1.0/24(rw,sync,root_squash,no_subtree_check)
Apply:
sudo exportfs -r
sudo systemctl enable --now nfs-server
sudo exportfs -v
Example output:
/opt/shared 192.168.1.0/24(sync,wdelay,no_subtree_check,sec=sys,rw,root_squash,no_all_squash)
Install client tools:
sudo dnf install nfs-utils
Create mount point:
sudo mkdir -p /mnt/nfs_shared
Mount:
sudo mount -t nfs4 192.168.1.100:/opt/shared /mnt/nfs_shared
Check:
findmnt /mnt/nfs_shared
cat /mnt/nfs_shared/hello.txt
Example output:
hello from server
Interpretation:
Show what happens when the client IP is not allowed by /etc/exports.
On the server, restrict the export to the wrong network:
/opt/shared 10.10.10.0/24(rw,sync,root_squash)
Apply:
sudo exportfs -r
On the client:
sudo mount -t nfs 192.168.1.100:/opt/shared /mnt/nfs_shared
Example output:
mount.nfs: access denied by server while mounting 192.168.1.100:/opt/shared
sudo exportfs -v
Example output:
/opt/shared 10.10.10.0/24(rw,sync,root_squash)
Interpretation:
Use the correct client subnet or IP:
/opt/shared 192.168.1.0/24(rw,sync,root_squash,no_subtree_check)
Apply:
sudo exportfs -r
Diagnose when the export is correct but the client cannot reach NFS services.
On the server, remove NFS firewall services:
sudo firewall-cmd --permanent --remove-service=nfs
sudo firewall-cmd --permanent --remove-service=mountd
sudo firewall-cmd --permanent --remove-service=rpc-bind
sudo firewall-cmd --reload
On the client:
sudo mount -t nfs 192.168.1.100:/opt/shared /mnt/nfs_shared
Possible output:
mount.nfs: Connection timed out
nc -vz 192.168.1.100 2049
Example output:
nc: connect to 192.168.1.100 port 2049 (tcp) timed out
sudo firewall-cmd --list-services
Example output:
ssh dhcpv6-client
Interpretation:
sudo firewall-cmd --permanent --add-service=nfs
sudo firewall-cmd --permanent --add-service=mountd
sudo firewall-cmd --permanent --add-service=rpc-bind
sudo firewall-cmd --reload
Retest:
nc -vz 192.168.1.100 2049
Expected:
Connection to 192.168.1.100 2049 port [tcp/nfs] succeeded!
Show why matching usernames is not enough if numeric UIDs differ.
On server:
alice UID = 1001
On client:
alice UID = 1002
The server directory is owned by UID 1001:
ls -ln /opt/shared
Example output on server:
drwxr-x--- 2 1001 1001 4096 Jun 1 12:00 /opt/shared
On client, Alice tries:
touch /mnt/nfs_shared/test.txt
Example output:
touch: cannot touch '/mnt/nfs_shared/test.txt': Permission denied
On client:
id alice
Example:
uid=1002(alice) gid=1002(alice)
On server:
id alice
Example:
uid=1001(alice) gid=1001(alice)
Interpretation:
Show why root on the client may not have root power on the NFS export.
/opt/shared 192.168.1.0/24(rw,sync,root_squash)
Apply:
sudo exportfs -r
On client as root:
sudo touch /mnt/nfs_shared/root-created.txt
Possible output:
touch: cannot touch '/mnt/nfs_shared/root-created.txt': Permission denied
Or if the directory allows anonymous writes, check ownership:
ls -ln /mnt/nfs_shared/root-created.txt
Example output:
-rw-r--r-- 1 65534 65534 0 Jun 1 12:30 root-created.txt
Interpretation:
/opt/shared 192.168.1.0/24(rw,sync,no_root_squash)
Warning:
Understand what happens when the server-side exported directory changes while clients still have old references.
Client mounts:
sudo mount -t nfs 192.168.1.100:/opt/shared /mnt/nfs_shared
cd /mnt/nfs_shared
On the server, rename and recreate the export directory:
sudo mv /opt/shared /opt/shared.old
sudo mkdir /opt/shared
sudo exportfs -r
On the client:
ls
Possible output:
ls: cannot access '.': Stale file handle
Interpretation:
On the client:
cd /
sudo umount /mnt/nfs_shared
sudo mount /mnt/nfs_shared
If unmount is busy:
sudo lsof +f -- /mnt/nfs_shared
sudo fuser -vm /mnt/nfs_shared
Then stop the using process or move out of the directory.
/etc/fstabShow why NFS mounts should be configured carefully for boot.
192.168.1.100:/opt/shared /mnt/nfs_shared nfs defaults 0 0
If the NFS server is down during boot, the client may wait for a long time.
192.168.1.100:/opt/shared /mnt/nfs_shared nfs4 defaults,_netdev,nofail,x-systemd.automount 0 0
sudo systemctl daemon-reload
sudo mount -a
Check systemd mount units:
systemctl list-units | grep nfs_shared
Example output:
mnt-nfs_shared.automount loaded active waiting /mnt/nfs_shared
Interpretation:
Show how export options override client expectations.
/opt/shared 192.168.1.0/24(ro,sync,root_squash)
Apply:
sudo exportfs -r
Client remount:
sudo umount /mnt/nfs_shared
sudo mount -t nfs 192.168.1.100:/opt/shared /mnt/nfs_shared
Try write:
touch /mnt/nfs_shared/test.txt
Example output:
touch: cannot touch '/mnt/nfs_shared/test.txt': Read-only file system
findmnt /mnt/nfs_shared
Example output:
TARGET SOURCE FSTYPE OPTIONS
/mnt/nfs_shared 192.168.1.100:/opt/shared nfs4 ro,relatime,vers=4.2
Interpretation:
Check whether NFS is slow and where the bottleneck may be.
On the client:
dd if=/dev/zero of=/mnt/nfs_shared/testfile bs=1M count=512 conv=fdatasync
Example output:
536870912 bytes copied, 8.2 s, 65.5 MB/s
dd if=/mnt/nfs_shared/testfile of=/dev/null bs=1M
Example output:
536870912 bytes copied, 4.1 s, 130 MB/s
nfsiostat 1
Example output:
op/s rpc bklog
120.00 0.00
read: avg RTT 4.0 ms avg exe 5.0 ms
write: avg RTT 12.0 ms avg exe 15.0 ms
Interpretation:
On client:
mount | grep nfs
nfsstat -c
On server:
nfsstat -s
iostat -xz 1
Diagnose a client that hangs or reports server not responding.
Client log or terminal shows:
nfs: server 192.168.1.100 not responding, still trying
ping 192.168.1.100
Check NFS port:
nc -vz 192.168.1.100 2049
Example failure:
nc: connect to 192.168.1.100 port 2049 failed: No route to host
On server:
systemctl status nfs-server
sudo ss -tulnp | grep 2049
Example output:
tcp LISTEN 0 64 0.0.0.0:2049 0.0.0.0:*
Interpretation:
showmount to Inspect ExportsCheck what the server appears to export.
On client:
showmount -e 192.168.1.100
Example output:
Export list for 192.168.1.100:
/opt/shared 192.168.1.0/24
Interpretation:
Important note:
Stop sharing a directory without editing many files manually.
Check current exports:
sudo exportfs -v
Unexport:
sudo exportfs -u 192.168.1.0/24:/opt/shared
Check again:
sudo exportfs -v
Interpretation:
For a permanent stop, remove or comment out the line in /etc/exports.
Symptoms:
mount.nfs: access denied by server
Check:
sudo exportfs -v
cat /etc/exports
showmount -e SERVER
Likely causes:
Fix:
sudo exportfs -r
and correct /etc/exports.
Symptoms:
mount.nfs: Connection timed out
Check:
ping SERVER
nc -vz SERVER 2049
systemctl status nfs-server
sudo firewall-cmd --list-services
Likely causes:
Symptoms:
touch: Permission denied
Check:
id
ls -ln /mnt/nfs_shared
ls -ln /opt/shared
sudo exportfs -v
Likely causes:
Symptoms:
-rw-r--r-- 1 nobody nobody file.txt
or numeric:
4294967294
Likely causes:
Check:
cat /etc/idmapd.conf
id username
nfsidmap -l
Symptoms:
Stale file handle
Likely causes:
Fix:
cd /
sudo umount /mnt/nfs_shared
sudo mount /mnt/nfs_shared
Symptoms:
Fix fstab with:
Example:
192.168.1.100:/opt/shared /mnt/nfs_shared nfs4 defaults,_netdev,nofail,x-systemd.automount 0 0
When NFS fails, troubleshoot in layers.
Useful commands:
ping SERVER
nc -vz SERVER 2049
systemctl status nfs-server
sudo exportfs -v
showmount -e SERVER
mount | grep nfs
findmnt /mnt/nfs_shared
id
ls -ln
journalctl -u nfs-server -b
dmesg -T | grep -i nfs
Server setup:
sudo dnf install nfs-utils
sudo mkdir -p /opt/shared
sudo vi /etc/exports
sudo exportfs -r
sudo exportfs -v
sudo systemctl enable --now nfs-server
Client setup:
sudo dnf install nfs-utils
sudo mkdir -p /mnt/nfs_shared
sudo mount -t nfs4 SERVER:/opt/shared /mnt/nfs_shared
findmnt /mnt/nfs_shared
Firewall:
sudo firewall-cmd --permanent --add-service=nfs
sudo firewall-cmd --permanent --add-service=mountd
sudo firewall-cmd --permanent --add-service=rpc-bind
sudo firewall-cmd --reload
NFS inspection:
sudo exportfs -v
showmount -e SERVER
nfsstat -s
nfsstat -c
nfsiostat 1
mount | grep nfs
Persistent mount:
SERVER:/opt/shared /mnt/nfs_shared nfs4 defaults,_netdev,nofail,x-systemd.automount 0 0
Unmount:
sudo umount /mnt/nfs_shared
Force investigation if busy:
sudo lsof +f -- /mnt/nfs_shared
sudo fuser -vm /mnt/nfs_shared
On the client:
cd /
sudo umount /mnt/nfs_shared 2>/dev/null
sudo rmdir /mnt/nfs_shared 2>/dev/null
Remove fstab test entry if added:
sudo vi /etc/fstab
sudo systemctl daemon-reload
On the server, remove export line from /etc/exports, then:
sudo exportfs -r
sudo exportfs -v
Optionally remove test directory:
sudo rm -rf /opt/shared
/opt/shared to one trusted client IP./mnt/nfs_shared and verify it with findmnt.rw to ro, reload exports, remount on the client, and explain the write failure./etc/exports and diagnose the resulting access denied by server error./etc/fstab using _netdev,nofail,x-systemd.automount, then test it with mount -a.nfsstat or nfsiostat to observe NFS activity during a file copy.