$linuxjunkies
>

How to Set Up an NFS Server

Set up a production-ready NFSv4 server on Linux: configure exports, mount shares on clients, lock down permissions, and tune read/write performance.

IntermediateUbuntuDebianFedoraArch9 min readUpdated June 7, 2026

Before you start

  • Root or sudo access on both server and client machines
  • Server and clients on the same LAN or routable network with known IP addresses
  • A data directory already created on the server with content to share

NFS (Network File System) lets Linux machines share directories over a network as if they were local filesystems. It is still the workhorse protocol for shared storage in home labs, university clusters, and production environments alike. This guide walks through installing and configuring an NFSv4 server, exporting shares with sensible permissions, mounting them on clients, and tuning a few knobs that matter most for throughput.

Install NFS Server and Client Packages

Debian / Ubuntu

sudo apt update
sudo apt install nfs-kernel-server nfs-common

Fedora / RHEL / Rocky Linux

sudo dnf install nfs-utils

Arch Linux

sudo pacman -S nfs-utils

On client-only machines, you only need nfs-common (Debian/Ubuntu) or nfs-utils (Fedora/Arch). The kernel NFS server module is loaded automatically when the daemon starts; no manual modprobe is needed on modern kernels.

Create and Prepare the Export Directory

Pick a dedicated root for your exports. Using a single NFS pseudo-root (/srv/nfs) and bind-mounting real directories into it is the cleanest NFSv4 pattern — it confines what clients can traverse.

sudo mkdir -p /srv/nfs/shared
sudo mkdir -p /data/shared          # your actual data lives here
sudo mount --bind /data/shared /srv/nfs/shared

Make the bind mount persistent by adding it to /etc/fstab:

/data/shared  /srv/nfs/shared  none  bind  0  0

Set Ownership and Permissions

Decide on an ownership model before touching /etc/exports. Two common approaches:

  • Shared group: set a common GID (e.g., nfsshare with GID 2000) on both server and all clients.
  • nobody/nogroup mapping: use all_squash to map all remote users to a single anonymous UID — simpler but less granular.
# Shared-group example
sudo groupadd -g 2000 nfsshare
sudo chown root:nfsshare /data/shared
sudo chmod 2775 /data/shared        # setgid bit so new files inherit the group

Configure /etc/exports

/etc/exports is the single file that controls what is shared, to whom, and under what conditions. Each line follows the pattern:

# /path/to/export  client(options)  another_client(options)

A realistic example exporting to a trusted LAN subnet:

# /etc/exports

# NFSv4 pseudo-root — must be exported with fsid=0
/srv/nfs          192.168.1.0/24(ro,sync,no_subtree_check,crossmnt,fsid=0)

# Shared read-write directory
/srv/nfs/shared   192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)

Key options explained:

  • sync — write data to disk before acknowledging the client. Slower than async but prevents data corruption on crash. Use async only for scratch data where loss is acceptable.
  • no_subtree_check — disables subtree checking, which causes more problems than it solves on modern kernels. Always set this.
  • fsid=0 — marks this path as the NFSv4 pseudo-root. Required for NFSv4; only one export gets this.
  • crossmnt — allows clients to cross mount points within the pseudo-root tree without separate export entries.
  • no_root_squash — remote root keeps UID 0 on the server. Use only on trusted machines; omit it for public or semi-trusted clients.
  • root_squash (the default) — maps remote root to nobody.
  • all_squash — maps every remote user to nobody/nogroup. Good for public read-only shares.

After editing, apply the changes without restarting the daemon:

sudo exportfs -rav

Enable and Start the NFS Server

Debian / Ubuntu

sudo systemctl enable --now nfs-kernel-server

Fedora / RHEL / Rocky / Arch

sudo systemctl enable --now nfs-server

Check that the daemon is healthy:

sudo systemctl status nfs-server   # or nfs-kernel-server on Debian/Ubuntu
sudo exportfs -v                   # list active exports with their options

Open the Firewall

NFSv4 only needs TCP port 2049. NFSv3 additionally requires portmapper (111) and dynamically assigned ports — another reason to prefer v4.

firewalld (Fedora / RHEL / Rocky)

sudo firewall-cmd --permanent --add-service=nfs
sudo firewall-cmd --reload

ufw (Ubuntu / Debian)

sudo ufw allow from 192.168.1.0/24 to any port 2049 proto tcp
sudo ufw reload

nftables (manual / Arch)

sudo nft add rule inet filter input ip saddr 192.168.1.0/24 tcp dport 2049 accept

Mount the Share on a Client

One-time mount for testing

sudo mkdir -p /mnt/shared
sudo mount -t nfs4 -o rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 \
  192.168.1.10:/shared /mnt/shared

Replace 192.168.1.10 with your server's IP or hostname. Notice the NFSv4 mount path is relative to the pseudo-root (/shared, not /srv/nfs/shared).

Persistent mount via /etc/fstab

192.168.1.10:/shared  /mnt/shared  nfs4  rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev  0  0

The _netdev option tells systemd to mount this only after the network is up — critical for avoiding boot hangs.

Persistent mount via systemd .mount unit (alternative)

# /etc/systemd/system/mnt-shared.mount
[Unit]
Description=NFS shared mount
After=network-online.target
Wants=network-online.target

[Mount]
What=192.168.1.10:/shared
Where=/mnt/shared
Type=nfs4
Options=rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now mnt-shared.mount

Performance Basics

  • rsize / wsize: Read and write buffer sizes. 1 MiB (1048576) is a safe modern default; Gigabit+ networks can benefit from this over the kernel's default of 131072 bytes.
  • nconnect: Available since Linux 5.3, this option opens multiple TCP connections to the server. nconnect=4 can significantly improve throughput on high-latency or high-bandwidth links. Add it to mount options.
  • Number of NFS threads: The default is 8 threads. On busy servers, increase this in /etc/nfs.conf (or /etc/default/nfs-kernel-server on older Debian):
# /etc/nfs.conf  (modern, cross-distro)
[nfsd]
threads=32
sudo systemctl restart nfs-server   # apply the change
  • async exports: Use async instead of sync for temporary or scratch shares to trade durability for speed. Never use async on a share containing databases or important data.

Verify the Setup

# On the server — confirm exports are live
sudo exportfs -v

# On the client — check the mount is active
mount | grep nfs
df -h /mnt/shared

# Quick read/write test
dd if=/dev/zero of=/mnt/shared/testfile bs=1M count=256 oflag=direct
rm /mnt/shared/testfile

Expected exportfs -v output will list each export path, client pattern, and active options. The dd test gives a rough write throughput figure — compare it against a local disk write to gauge network overhead.

Troubleshooting

Mount hangs or times out

  • Confirm port 2049 is reachable: nc -zv 192.168.1.10 2049
  • Check the firewall on the server. On RHEL/Rocky, firewall-cmd --list-services should show nfs.
  • Verify the NFS server service is running: systemctl status nfs-server

Permission denied on the mount

  • Run sudo exportfs -v and confirm the client's IP is covered by an export rule.
  • Check UIDs match between server and client for the relevant user. NFS has no user-translation layer by default — UID 1001 on the client maps to UID 1001 on the server.
  • If you used root_squash, root on the client will be mapped to nobody and may lack write permission.

Stale file handle errors

These appear after the server reboots or the export path changes while a client has it mounted. Unmount and remount the share on the client: sudo umount -l /mnt/shared && sudo mount /mnt/shared. The -l flag does a lazy unmount, clearing the stale handle.

Boot hangs on client

Ensure _netdev is in the fstab mount options, or use a systemd .mount unit with After=network-online.target. Without this, the filesystem layer tries to mount before the network interface is configured.

tested on:Ubuntu 24.04Debian 12Fedora 40Rocky 9

Frequently asked questions

Should I use NFSv3 or NFSv4?
Use NFSv4. It needs only one TCP port (2049), supports stateful locking natively, handles firewalls better, and has a cleaner pseudo-root model. NFSv3 is useful only for legacy clients that cannot support v4.
Why do files created on the client show the wrong owner?
NFS maps users by UID number, not username. If UID 1001 is 'alice' on the client but 'bob' on the server, files will appear owned by 'bob' on the server. Keep UIDs in sync across machines, use LDAP/FreeIPA for centralised identity, or use all_squash for simpler setups.
What is the difference between sync and async export options?
sync tells the server to flush writes to disk before acknowledging the client, protecting against data loss if the server crashes. async acknowledges before the flush, giving higher throughput but risking corruption. Always use sync for important data.
How do I check how many NFS threads are currently running?
Run 'cat /proc/net/rpc/nfsd' and look at the 'th' line, or use 'nfsstat -s' to see server-side statistics including thread usage. If threads are consistently saturated, increase the count in /etc/nfs.conf.
Can I export a ZFS or Btrfs dataset directly instead of using a bind mount?
Yes. You can export any directory path regardless of the underlying filesystem. The bind-mount pattern is a convention for clean NFSv4 pseudo-root management, not a technical requirement. ZFS users often export datasets directly and set sharenfs= properties via zfs set.

Related guides