An NFS export is a directory on the server plus a list of clients allowed to reach it; the file that describes it is not what serves it. /etc/exports is read by exportfs, which writes the master export table to /var/lib/nfs/etab, which rpc.mountd consults on a client’s MOUNT request. Three files: one that people edit, two that decide what happens.

That indirection produces the most common NFS incident: the export list looks right and the client is refused, because the file changed and the table did not. The rest of the configuration comes down to two questions — which identity the server thinks a request carries, and whether a reply that said “written” survives a crash.

The file and the table

An /etc/exports line is <directory> <client>[(<options>)] ..., so /srv/data 10.0.0.0/24(rw,sync,no_subtree_check,root_squash) exports one directory to one network with four options, and another client group can follow on the same line with its own options. Clients are hostnames, wildcards, CIDR networks or netgroups; comments run to end of line, and a trailing backslash continues a line. Nothing in the file is live until exportfs -ra rebuilds the table from it, and exportfs -v prints the table that is actually loaded.

showmount -e shows that same table from the client side: it asks the mount daemon, not the file, so it answers “what does the server think it is exporting”, not “what did I just write”. The fixture reproduces the trap: after appending /srv/newshare without re-exporting, the file has four entries and the table three, so a client asking for it gets NOENT.

What the options actually decide

ro and rw set the write direction, and read-only is the default: without rw, any request that changes the filesystem is disallowed. sync and async decide when the server may answer — with sync the reply follows the commit to stable storage, with async it may precede the commit, which the documentation says allows a crash to lose acknowledged writes. async was the default up to nfs-utils 1.0.0; sync has been the default since.

The squash options are the identity policy. root_squash maps a remote uid 0 to the anonymous uid, which is what you want almost everywhere; no_root_squash lets a client’s root be root on the server, defensible only for a host you trust; all_squash with anonuid/anongid maps every client id to one anonymous identity, the right shape for a public share. subtree_check versus no_subtree_check is a reliability knob, not a security one: subtree checking verifies that a requested file is still inside the exported subtree, which can break access to files renamed while open; since nfs-utils 1.1.0 the default is no_subtree_check.

Mounting, and the id mapping trap

mount -t nfs -o vers=4.2 server:/srv/data /mnt/data attaches the export; with NFSv4 you export once with fsid=0 and mount subtrees by path. Prefer hard — the default — because a soft mount gives up after retrans retransmissions and returns EIO, which the man page warns can corrupt data silently; the price is that a dead server turns an ls into a process stuck in D state. intr no longer helps: it has been ignored since kernel 2.6.25.

Permissions are decided by numeric uid and gid against the server’s accounts, not by names, so a client whose id has no counterpart there lands on the anonymous uid. id on both sides plus ls -ln on the mount is the check: a file you own showing as 65534 means the mapping is wrong, not the mode.

When it breaks

The classic failure is the stale export table, in both directions. A share added and never exported is invisible; a share removed and never re-exported is still served, to clients you believe you locked out. Both are fixed by exportfs -ra, and both are visible by diffing /etc/exports against exportfs -v.

The second failure is the hung client. A hard mount against an unreachable server blocks in the kernel indefinitely, so every process touching the mount is unkillable and the machine accumulates D-state processes; the fixture’s showmount -e 127.0.0.1 took 35 seconds to fail, the same RPC timeout that makes a bad mount look like a frozen ls. The tempting fix, soft, trades the hang for silent write corruption.

Check it quickly

Compare the loaded table against the file, then check the ids:

exportfs -v
showmount -e localhost
exportfs -ra && exportfs -v
mount | grep 'type nfs'
id; ls -ln /mnt/data | head

exportfs -v must list exactly the directories and options in /etc/exports; anything missing means the table is stale and clients get NOENT while the file looks correct. showmount -e should print the same directories to a remote caller. On the client, id and ls -ln must agree on the numeric owner — 65534 or nobody means the server has no matching account.

Sources

  • exports(5), export syntax, squash options, sync/async and the subtree_check defaults, checked 2026-09-16.
  • exportfs(8), the master export table in /var/lib/nfs/etab and how exportfs maintains it, checked 2026-09-16.
  • nfs(5), mount options including hard/soft, timeo and the obsolescence of intr, checked 2026-09-16.
  • showmount(8), querying the server’s export list from a client, checked 2026-09-16.