<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Nfs · XB Field Notes</title><link>https://www.xbcatm.com/tags/nfs/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://www.xbcatm.com/tags/nfs/index.xml" rel="self" type="application/rss+xml"/><item><title>NFS shares a remote directory over the network</title><link>https://www.xbcatm.com/posts/nfs-exporting-directories/</link><pubDate>Wed, 23 Jun 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/nfs-exporting-directories/</guid><description>NFS exports a directory so clients mount it as a local path, keeping one authoritative copy under a single server.</description><content:encoded>&lt;p&gt;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. &lt;code&gt;/etc/exports&lt;/code&gt; is read by &lt;code&gt;exportfs&lt;/code&gt;, which writes the master export table to &lt;code&gt;/var/lib/nfs/etab&lt;/code&gt;, which &lt;code&gt;rpc.mountd&lt;/code&gt; consults on a client&amp;rsquo;s MOUNT request. Three files: one that people edit, two that decide what happens.&lt;/p&gt;
&lt;p&gt;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 &amp;ldquo;written&amp;rdquo; survives a crash.&lt;/p&gt;
&lt;h2 id="the-file-and-the-table"&gt;The file and the table&lt;/h2&gt;
&lt;p&gt;An &lt;code&gt;/etc/exports&lt;/code&gt; line is &lt;code&gt;&amp;lt;directory&amp;gt; &amp;lt;client&amp;gt;[(&amp;lt;options&amp;gt;)] ...&lt;/code&gt;, so &lt;code&gt;/srv/data 10.0.0.0/24(rw,sync,no_subtree_check,root_squash)&lt;/code&gt; 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 &lt;code&gt;exportfs -ra&lt;/code&gt; rebuilds the table from it, and &lt;code&gt;exportfs -v&lt;/code&gt; prints the table that is actually loaded.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;showmount -e&lt;/code&gt; shows that same table from the client side: it asks the mount daemon, not the file, so it answers &amp;ldquo;what does the server think it is exporting&amp;rdquo;, not &amp;ldquo;what did I just write&amp;rdquo;. The fixture reproduces the trap: after appending &lt;code&gt;/srv/newshare&lt;/code&gt; without re-exporting, the file has four entries and the table three, so a client asking for it gets NOENT.&lt;/p&gt;
&lt;h2 id="what-the-options-actually-decide"&gt;What the options actually decide&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ro&lt;/code&gt; and &lt;code&gt;rw&lt;/code&gt; set the write direction, and read-only is the default: without &lt;code&gt;rw&lt;/code&gt;, any request that changes the filesystem is disallowed. &lt;code&gt;sync&lt;/code&gt; and &lt;code&gt;async&lt;/code&gt; decide when the server may answer — with &lt;code&gt;sync&lt;/code&gt; the reply follows the commit to stable storage, with &lt;code&gt;async&lt;/code&gt; it may precede the commit, which the documentation says allows a crash to lose acknowledged writes. &lt;code&gt;async&lt;/code&gt; was the default up to nfs-utils 1.0.0; &lt;code&gt;sync&lt;/code&gt; has been the default since.&lt;/p&gt;
&lt;p&gt;The squash options are the identity policy. &lt;code&gt;root_squash&lt;/code&gt; maps a remote uid 0 to the anonymous uid, which is what you want almost everywhere; &lt;code&gt;no_root_squash&lt;/code&gt; lets a client&amp;rsquo;s root be root on the server, defensible only for a host you trust; &lt;code&gt;all_squash&lt;/code&gt; with &lt;code&gt;anonuid&lt;/code&gt;/&lt;code&gt;anongid&lt;/code&gt; maps every client id to one anonymous identity, the right shape for a public share. &lt;code&gt;subtree_check&lt;/code&gt; versus &lt;code&gt;no_subtree_check&lt;/code&gt; 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 &lt;code&gt;no_subtree_check&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="mounting-and-the-id-mapping-trap"&gt;Mounting, and the id mapping trap&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;mount -t nfs -o vers=4.2 server:/srv/data /mnt/data&lt;/code&gt; attaches the export; with NFSv4 you export once with &lt;code&gt;fsid=0&lt;/code&gt; and mount subtrees by path. Prefer &lt;code&gt;hard&lt;/code&gt; — the default — because a &lt;code&gt;soft&lt;/code&gt; mount gives up after &lt;code&gt;retrans&lt;/code&gt; retransmissions and returns EIO, which the man page warns can corrupt data silently; the price is that a dead server turns an &lt;code&gt;ls&lt;/code&gt; into a process stuck in &lt;code&gt;D&lt;/code&gt; state. &lt;code&gt;intr&lt;/code&gt; no longer helps: it has been ignored since kernel 2.6.25.&lt;/p&gt;
&lt;p&gt;Permissions are decided by numeric uid and gid against the server&amp;rsquo;s accounts, not by names, so a client whose id has no counterpart there lands on the anonymous uid. &lt;code&gt;id&lt;/code&gt; on both sides plus &lt;code&gt;ls -ln&lt;/code&gt; on the mount is the check: a file you own showing as 65534 means the mapping is wrong, not the mode.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;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 &lt;code&gt;exportfs -ra&lt;/code&gt;, and both are visible by diffing &lt;code&gt;/etc/exports&lt;/code&gt; against &lt;code&gt;exportfs -v&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The second failure is the hung client. A &lt;code&gt;hard&lt;/code&gt; mount against an unreachable server blocks in the kernel indefinitely, so every process touching the mount is unkillable and the machine accumulates &lt;code&gt;D&lt;/code&gt;-state processes; the fixture&amp;rsquo;s &lt;code&gt;showmount -e 127.0.0.1&lt;/code&gt; took 35 seconds to fail, the same RPC timeout that makes a bad mount look like a frozen &lt;code&gt;ls&lt;/code&gt;. The tempting fix, &lt;code&gt;soft&lt;/code&gt;, trades the hang for silent write corruption.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;Compare the loaded table against the file, then check the ids:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;exportfs -v
showmount -e localhost
exportfs -ra &amp;amp;&amp;amp; exportfs -v
mount | grep &amp;#39;type nfs&amp;#39;
id; ls -ln /mnt/data | head
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;exportfs -v&lt;/code&gt; must list exactly the directories and options in &lt;code&gt;/etc/exports&lt;/code&gt;; anything missing means the table is stale and clients get NOENT while the file looks correct. &lt;code&gt;showmount -e&lt;/code&gt; should print the same directories to a remote caller. On the client, &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;ls -ln&lt;/code&gt; must agree on the numeric owner — 65534 or nobody means the server has no matching account.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man5/exports.5.html"&gt;exports(5)&lt;/a&gt;, export syntax, squash options, sync/async and the subtree_check defaults, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man8/exportfs.8.html"&gt;exportfs(8)&lt;/a&gt;, the master export table in /var/lib/nfs/etab and how exportfs maintains it, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man5/nfs.5.html"&gt;nfs(5)&lt;/a&gt;, mount options including hard/soft, timeo and the obsolescence of intr, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man8/showmount.8.html"&gt;showmount(8)&lt;/a&gt;, querying the server&amp;rsquo;s export list from a client, checked 2026-09-16.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>