What is Mount Root Filesystem via NFS-Free Embedded Linux Course online

PREV_LEC NEXT_LEC
Mount Root Filesystem via NFS
A free embedded Linux course lecture — skip the reflash cycle by booting your target straight off your development machine
Protocol: NFS
Use case: development loop
Tested with: QEMU + real board

Reflashing an SD card every time you rebuild BusyBox or add one file gets old fast. An NFS root filesystem lets your target board mount its / straight from a directory on your host over the network — edit a file on the host, and it’s live on the target instantly, no re-imaging required. This lecture in our free linux development course covers kernel configuration, host-side NFS server setup, and testing on both QEMU and real hardware.

NFS root filesystem CONFIG_ROOT_NFS nfsroot kernel parameter free linux kernel development course free embedded systems course QEMU networking

What You Will Learn

Lecture Outcomes

Kernel config required for an NFS root The three boot parameters that control it Setting up an NFS server on your host Testing an NFS root under QEMU Testing on real target hardware via U-Boot Fixing the file-ownership mess NFS root creates

Prerequisites

You’ll need a staging directory from earlier in this root filesystem image series, a Linux host that can run an NFS server, and either QEMU or a real board with a network interface and a bootloader that can set kernel command-line arguments. A working host-to-target network path — a QEMU tap interface or a real Ethernet cable — is required before any of this will boot.

Why Use NFS Root at All

Once your board can reach the network at boot, mounting root over NFS turns the entire development loop into a simple file save. There’s no image to rebuild, no card to remove and reflash, and any debug tool you drop into the exported directory shows up on the target on the next access — no reboot needed either, in most cases. The trade-off is that this is strictly a development technique: production devices should ship a self-contained image, not depend on a development host being reachable at boot.

NFS Root Boot Sequence
bootloader loads kernel + dtb → kernel brings up NIC (ip=) → kernel mounts nfsroot= over the network → init starts from host’s export

Kernel Configuration

The NFS client has to be built into the kernel itself — not as a module — because root has to be mounted before any module can be loaded from the (not yet mounted) root filesystem. Enable it with:

CONFIG_NFS_FS=y
CONFIG_ROOT_NFS=y
CONFIG_IP_PNP=y

CONFIG_IP_PNP is optional if you plan to set the IP address explicitly on the kernel command line (which is what we do below), but it’s worth enabling if you ever want DHCP-based boot instead.

The Three Boot Parameters

ParameterPurposeExample
root=/dev/nfsTells the kernel the root device is an NFS mount, not a block deviceroot=/dev/nfs
nfsroot=Server IP and exported path to mount as /nfsroot=192.168.7.1:/srv/nfs/target-root
ip=Brings the target’s network interface up early enough for the NFS mount to succeed, before init runsip=192.168.7.101::192.168.7.1:255.255.255.0::eth0:off

The full ip= form is ip=<client-ip>:<server-ip>:<gateway>:<netmask>:<hostname>:<device>:<autoconf>. Leaving fields empty (as above) just skips them; here we’re setting a static IP with no hostname and no DHCP fallback (off).

Setting Up the NFS Server on Your Host

Install the server package and point it at a directory holding a full staging tree — a fresh copy, not your working staging directory, so build tooling doesn’t collide with a live target:

$ sudo apt-get install nfs-kernel-server
$ sudo cp -a staging /srv/nfs/target-root

Export the directory by adding a line to /etc/exports:

/srv/nfs/target-root 192.168.7.0/24(rw,sync,no_subtree_check,no_root_squash)
OptionMeaning
rwTarget can write to the export, not just read
syncServer acknowledges writes only once they’re actually on disk — safer, slightly slower
no_subtree_checkSkips a subtree-consistency check that’s mostly relevant when exporting a subdirectory of a larger filesystem
no_root_squashLets the target’s root user act as root on the export — needed during development, dangerous on an untrusted network

Reload the exports and restart the service:

$ sudo exportfs -ra
$ sudo systemctl restart nfs-kernel-server

Testing Under QEMU

QEMU needs a virtual network device bridged to a tap interface so the guest kernel can actually reach the host’s NFS export. This original script sets up the tap interface, then launches an aarch64 target under QEMU with an NFS root:

#!/bin/bash
KERNEL=Image
DTB=virt.dtb
EXPORT_DIR=/srv/nfs/target-root

HOST_IP=192.168.7.1
TARGET_IP=192.168.7.101
NET=192.168.7.0
MASK=255.255.255.0

sudo ip tuntap add dev tap0 mode tap user "$(whoami)"
sudo ip addr add ${HOST_IP}/24 dev tap0
sudo ip link set tap0 up
sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"

qemu-system-aarch64 -M virt -cpu cortex-a53 -m 512M -nographic \
  -kernel "$KERNEL" -dtb "$DTB" \
  -append "console=ttyAMA0 root=/dev/nfs rw nfsroot=${HOST_IP}:${EXPORT_DIR} ip=${TARGET_IP}::${HOST_IP}:${MASK}::eth0:off" \
  -netdev tap,id=net0,ifname=tap0,script=no \
  -device virtio-net-device,netdev=net0

Save this as run-qemu-nfsroot.sh. On boot, the guest brings up its virtio NIC using the static ip= parameters, then mounts /srv/nfs/target-root straight off the host as /. Any file you edit in that directory on the host is immediately visible inside the running guest.

Testing on Real Hardware

The same three parameters apply at a real board’s U-Boot prompt — set them as environment variables, then load and boot the kernel as usual:

=> setenv serverip 192.168.7.1
=> setenv ipaddr 192.168.7.101
=> setenv nfspath /srv/nfs/target-root
=> setenv bootargs console=ttyS0,115200 root=/dev/nfs rw nfsroot=${serverip}:${nfspath} ip=${ipaddr}::${serverip}:255.255.255.0::eth0:off
=> tftpboot 0x82000000 zImage
=> tftpboot 0x83000000 board.dtb
=> bootz 0x82000000 - 0x83000000

The File Ownership Problem

NFS root exposes a real ownership headache during development: files already in your staging tree are owned by your own host UID, typically 1000, so on the target they show up owned by that same numeric UID rather than root. Anything the target itself creates as root shows up owned by UID 0. The two views of the filesystem disagree about who owns what, and it only gets more confusing the longer the export is shared between a build host and a live target.

There’s no clean fix, only a working pattern: keep a separate, root-owned copy of the export dedicated to the running target, and rebuild it from your staging directory instead of exporting your staging directory directly:

$ sudo rm -rf /srv/nfs/target-root
$ sudo cp -a staging /srv/nfs/target-root
$ sudo chown -R 0:0 /srv/nfs/target-root

NFS Root vs. Image-Based Root

ApproachIteration speedNeeds host reachable at bootUse case
NFS rootInstant — edit and rerunYesActive development
SD/eMMC imageMinutes — rebuild and reflashNoField testing, staging
Signed production imageN/A — released, not iteratedNoShipped devices

Common Mistakes and Troubleshooting

  • Forgetting ip= — the kernel has no address configured yet at the point it tries to mount root, so the mount times out even though the server is fine.
  • Firewall blocking NFS/RPC ports — NFS uses several ports beyond 2049 for the older protocol versions; a host firewall dropping them looks identical to a network cabling problem from the target’s side.
  • Exporting the wrong path or forgetting exportfs -ra — changes to /etc/exports don’t take effect until you reload them.
  • Building NFS client support as a module — it must be built in (=y), since modules can’t load before root is mounted.

Best Practices

  • Keep the NFS export subnet isolated from anything untrusted — no_root_squash effectively hands out root on the export to anyone who can reach it.
  • Automate the “rebuild the root-owned export copy” step as part of your normal build script instead of doing it manually after every change.
  • Never carry NFS-root bootargs into a production image build — treat it strictly as a development-time root method.

Summary

An NFS root filesystem turns the rebuild-reflash-reboot loop into a simple file edit by mounting the target’s / straight from your host. It needs the NFS client built into the kernel, three boot parameters (root=/dev/nfs, nfsroot=, ip=), and a properly exported directory on the host — plus a deliberate workaround for the UID mismatch between your build host and the target. It’s purely a development technique; production devices still ship a real image, which is exactly what the previous lecture in this free linux device drivers course covered.

FAQ

Can NFS root support be built as a kernel module?

No — root has to mount before any module can load from the (not yet available) root filesystem, so NFS client support must be built in.

Why does my board hang waiting for the NFS mount?

Almost always a missing or wrong ip= parameter — without it the target has no network address yet when it tries to mount root.

Is no_root_squash safe to use?

Only on a trusted, isolated development network. It lets the target’s root user act as root on the export, which is exactly what you don’t want on a shared or untrusted network.

Should I ship a product with an NFS root filesystem?

No — production devices need to boot standalone. Use NFS root during development, then switch to a real image (like the one built in the previous lecture) for anything that ships.

Why do files on the target show up owned by a random-looking UID?

Because the export directory was built by your ordinary host user; the target simply sees the same numeric UID with no matching account, which is why a dedicated root-owned copy of the export avoids the confusion.

Does NFS root work the same way over QEMU and real hardware?

Yes — the kernel-side configuration and bootargs are identical; only how you bring up the network (tap interface for QEMU, a real cable for hardware) differs.

Continue the Free Embedded Linux Course

Next up: loading the kernel itself over the network with TFTP, so you’re not reflashing that either.

Next Lecture Browse the Full Course
PREV_LEC NEXT_LEC

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *