When files must be shared across multiple Linux machines, NFS is one of the least-fuss solutions: the server exports a directory, the client mounts it as a local path, and reads and writes feel just like working on a local disk. This post walks the complete NFS chain on Rocky Linux 9 — install, export, service startup, client mount, and cross-machine read/write — with the server and client each running in a rockylinux:9 container (custom subnet on the same host). All output below comes from that live run; the three typical errors in this post were deliberately reproduced by making mistakes on purpose.

Planning and Prerequisites

Fix the two machines’ roles, directories, and IPs first; every command below follows this table:

Server Role Directory IP address
Rocky Linux 9 Server /export/nfs 192.168.254.2
Rocky Linux 9 Client /data 192.168.254.3

Confirm three things before starting: the two machines can reach each other over the network (mutual ping succeeds); you have root or sudo privileges; and ports 2049 (NFS) and 111 (rpcbind) are not blocked by the firewall on the server side. A typical intranet meets all three.

Note: when reproducing in containers, the export directory must sit on a filesystem that supports NFS export (I used tmpfs), otherwise exportfs -r fails with does not support NFS export; physical xfs/ext4 filesystems don’t have this problem.

Step 1: Install nfs-utils on Both Ends

On Rocky 9, server and client install the same package; dnf install pulls rpcbind in as a dependency:

1
dnf install -y nfs-utils

Verification point: run rpm -q nfs-utils; you should see a version starting with nfs-utils-2.5.4-42.el9 (this run measured 2.5.4-42.el9.aarch64).

Step 2: Create the Server Directory and Write the Export Rule

Create the export directory and open up write permissions:

1
2
mkdir -p /export/nfs
chmod 666 /export/nfs

chmod 666 makes the directory readable and writable by all users so client writes don’t get stuck on permissions. Then write the sharing rule into /etc/exports by adding this line:

1
/export/nfs 192.168.254.0/24(rw,no_root_squash,no_all_squash,sync)

What the rule means: share /export/nfs with the 192.168.254.0/24 subnet; the parameters in parentheses are, in order —

  • rw: allow reads and writes
  • no_root_squash: client root users keep their root identity, no privilege-dropping mapping
  • no_all_squash: ordinary users keep their original identity, no uniform mapping to the anonymous user
  • sync: writes hit disk synchronously — safer data, slightly slower

Make sure the subnet matches the client’s actual IP: this line only allows 192.168.254.x machines to mount; clients outside the subnet are refused by the server (see the live error in the failure-escape section below). Apply the config after editing:

1
exportfs -r

Verification point: exportfs -r exits with no error output; running exportfs -v then lists /export/nfs together with all its parameters.

Figure 1

Step 3: Start the Services on the Server

NFS depends on the RPC machinery, so both rpcbind and nfs-server must start — rpcbind first:

1
2
systemctl start rpcbind
systemctl start nfs-server

Verification point: rpcinfo -p localhost should list the registration lines for nfs (program 100003, port 2049) and mountd — this service table is only visible while rpcbind is working; without rpcbind the table is empty and clients naturally cannot connect. Then run showmount -e localhost locally; you should see your own export list.

Figure 2

In production, also enable both services at boot so the share doesn’t quietly vanish after a restart:

1
systemctl enable rpcbind nfs-server

Step 4: Client Queries the Export List and Mounts

The client also needs nfs-utils installed and rpcbind started (an NFSv3 mount needs local rpc.statd running for file locking; without it the mount fails — see the second entry in the failure-escape section). Then create the mount point and first ask the server what it exports:

1
2
3
systemctl start rpcbind
mkdir /data
showmount -e 192.168.254.2

Verification point: showmount -e should list /export/nfs 192.168.254.0/24 — seeing the list proves both the network and the RPC path work; get this step passing before mounting and you save most of the troubleshooting time.

Figure 3

After confirming /export/nfs is visible, mount:

1
mount -t nfs 192.168.254.2:/export/nfs /data/

Verification point: in df -hT /data/, the filesystem column should show nfs, and the device column should read 192.168.254.2:/export/nfs.

After a successful mount, files written to /data actually land in the server’s /export/nfs. Cross-machine read/write verification: on the client run echo "hello from client" > /data/write-test.txt, then on the server run cat /export/nfs/write-test.txt — you should see the same line of content, proof the file really crossed the network onto the server’s disk.

Figure 4

Historical Version Differences (CentOS 7)

If you are still on CentOS 7, the whole flow is unchanged except for three details: the package manager is yum (from el8 on, dnf replaces yum with compatible usage); the nfs-utils version is much older (1.3.0 on el7, with slightly older protocol defaults — for cross-version mounts prefer an explicit -o vers=3 or vers=4); and the startup commands are exactly the same, still systemctl start rpcbind nfs-server. The original article was written on CentOS 7.6, and this same command set ran cleanly on a 7.6 client back then.

Post-Install Verification and Failure Escapes

The final acceptance of the whole chain comes down to one test: the client writes, and the server can read it. To roll back, tear it down in reverse order:

1
2
3
4
# Unmount on the client
umount /data/
# Stop and disable on the server
systemctl disable --now nfs-server rpcbind

Four high-frequency errors, each reproduced live or handled against real symptoms during this container run:

  • mount.nfs: access denied by server: the client IP is not inside the export subnet. Tested by changing the export to 192.168.0.0/24 and mounting from 192.168.254.3 — this error reproduced reliably. Check the /etc/exports subnet against the client’s actual IP; after fixing, exportfs -r applies it, no service restart needed.
  • mount.nfs: rpc.statd is not running: the client’s local rpcbind/statd isn’t up; NFSv3 mounts need it for lock negotiation. Most common on minimal installs and in containers: run systemctl start rpcbind and confirm rpc.statd is running; as a temporary bypass you can mount with -o nolock (at the cost of losing cross-machine file locking).
  • Mount succeeds but writes fail with Permission denied: the export options don’t grant the root identity. Tested by reverting the options to the default root_squash — client root writes immediately failed with Permission denied because root had been mapped to nobody. Check whether no_root_squash is configured as needed, and whether the directory permissions (chmod 666) were actually applied.
  • showmount times out or clnt_create fails: the server’s rpcbind or nfs-server isn’t fully up. Go back to the server and check systemctl status and firewall access to port 111.

Figure 5

Wrap-up

Looking back at the whole chain: install nfs-utils, write /etc/exports, start rpcbind + nfs-server, confirm with showmount on the client, then mount — every step has a matching “what you should see.” A few lessons to close: no_root_squash lets client root keep its root identity — in testing, turning it off blocked writes immediately, so enable it only on trusted intranets; after editing exports, exportfs -r applies immediately, no nfs-server restart needed; and asking first with showmount -e before mounting cuts most of the time spent troubleshooting failed mounts.


This post was rewritten from the author’s CSDN blog articles originally published between 2020 and 2024 on CSDN.