Zero Client: Boot kernel and root filesystem from network with a Raspberry Pi2 or Pi3

A Zero Client is a computer that has nothing on its permanent storage but a bootloader. Rather, it loads everything from the network.

With the method presented in this article, you will be able to boot a Raspberry Pi into a full Debian OS with nothing more on the SD card other than the Raspberry firmware files and the u-boot bootloader on a FAT file system. The Linux kernel and the actual OS will be served over the local ethernet network.

We will only focus on the Raspberry Pi 3, but the instructions should work with minor adaptations also on a Pi 2.

The following instructions assume that you have already built…

  1. a full root file system for the Raspberry
  2. a u-boot binary, and
  3. a Linux kernel

… based on my previous blog post. Thus, you should already have the following directory structure:

  |- rpi23-gen-image
  |- linux
  |- u-boot
  |- raspberry-firmware

We will do all the work inside of the ~/workspace directory.

Preparation of the SD card

You will only need a small SD card with a FAT filesystem on it. The actual storage of files in the running OS will be transparently done over the network. Mount the filesystem on /mnt/sdcard and do the following:

Copy firmware

cp ./raspberry-firmware/* /mnt/sdcard

Copy u-boot bootloader

cp ./u-boot/u-boot.bin /mnt/sdcard

Create config.txt

config.txt is the configuration file read by the Raspberry firmware blobs. Most importantly, it tells the firmware what kernel to load. “Kernel” is a misleading term here, since we will boot u-boot rather than the kernel.

Create /mnt/sdcard/config.txt with the following contents:


# boot u-boot kernel

# run in 64bit mode

# enable serial console

Make an universal boot script for the u-boot bootloader

To achieve maximum flexibility — to avoid the repetitive dance of manually removing the SD card, copying files to it, and re-inserting it — we will make an universal u-boot startup script that does nothing else than loading yet another u-boot script from the network. This way, there is nothing specific about the to-be-loaded Kernel or OS on the SD card at all.

Create a file boot.scr.mkimage  with the following contents:

setenv autoload no
setenv autostart no

setenv serverip

tftp 0x100000 /netboot-${serial#}.scr

source 0x100000

Replace the server IP with the actual static IP of your server. Note that this script does nothing else other than loading yet another script called netboot-${serial#}.scr  from the server. serial# is the serial number which u-boot extracts from the Raspberry Pi hardware. This is usually the ethernet network device HW address. This way, you can have separate startup scripts for several Raspberry Pi’s if you have more than one. To keep the setup simple, set the file name to something predictable.

Compile the script into an u-boot readable image:

./u-boot/tools/mkimage -A arm64 -O linux -T script \
-C none -a 0x00 -e 0x00 \
-d boot.scr.mkimage \

Copy boot.scr to the SD card:

cp boot.scr /mnt/sdcard

The SD card preparation is complete at this point. We will now focus on the serving of the files necessary for boot.

Preparation of the file server

Do all of the following as ‘root’ user on a regular PC running Debian 9 (“Stretch”). This PC will act as the “server”.  This server will serve the files necessary to network-boot the Raspberry.

The directory /srv/tftp will hold …

  • an u-boot start script file
  • the kernel uImage file
  • and the binary device tree file.

… to be served by a TFTP server.

mkdir /srv/tftp

The directory /srv/rootfs_rpi3 will hold our entire root file system to be served by a NFS server:

mkdir /srv/rootfs_rpi3

You will find installation instructions of both TFTP and NFS servers further down.

Serve the root file system

Let’s copy the pre-built root file system into the directory from where it will be served by the NFS server:

rsync -a ./rpi23-gen-image/images/stretch/build/chroot/ /srv/rootfs_rpi3

(notice the slash at the end of the source directory)

Fix the root file system for network booting

Edit /srv/rootfs_rpi3/etc/fstab  and comment out all lines. We don’t need to mount anything from the SD card.

When network-booting the Linux kernel, the kernel will configure the network device for us (either with a static IP or DHCP). Any userspace programs attempting to re-configure the network device will cause problems, i.e. a loss of conncection to the NFS server. Thus, we need to prevent systemd-networkd from managing the Ethernet device. Make the device unmanaged by removing the folowing ethernet configuration file:

rm /srv/rootfs_rpi3/etc/systemd/network/

If you don’t do that, you’ll get the following kernel message during boot:

nfs: server not responding, still trying

That is because systemd has shut down and then re-started the ethernet device. Apparently NFS transfers are sensitive to that.

In case you want to log into the chroot to make additional changes that can only be done from within (e.g. running systemctl scripts etc.), you can do:

cp /usr/bin/qemu-aarch64-static /srv/rpi3fs/usr/bin
LANG=C LC_ALL=C chroot /srv/rpi3fs

Serve Kernel uImage

In this step, we create a Linux kernel uImage that can be directly read by the u-boot bootloader. We read Image.gz directly from the Kernel source directory, and output it into the /srv/tftp directory where a TFTP server will serve it to the Raspberry:

./u-boot/tools/mkimage -A arm64 -O linux -T kernel \
-C gzip -a 0x80000 -e 0x80000 \
-d ./linux/arch/arm64/boot/Image.gz \

Serve device tree binary

The u-boot bootloader will also need to load the device tree binary and pass it to the Linux kernel, so copy that too into the /srv/tftp directory.

cp ./linux/arch/arm64/boot/dts/broadcom/bcm2837-rpi-3-b.dtb /srv/tftp/

Serve secondary u-boot script loading the kernel

Create a file netboot-rpi3.scr.mkimage with the following contents:

setenv autoload no
setenv autostart no

setenv serverip

setenv bootargs "earlyprintk console=tty1 dwc_otg.lpm_enable=0 root=/dev/nfs rw rootfstype=nfs nfsroot=,udp,vers=3 ip=dhcp nfsrootdebug smsc95xx.turbo_mode=N elevator=deadline rootdelay cma=256M@512M net.ifnames=1 init=/bin/systemd loglevel=7 systemd.log_level=debug systemd.log_target=console"

tftp ${kernel_addr_r} linux-rpi3.uImage
tftp ${fdt_addr_r} bcm2837-rpi-3-b.dtb
bootm ${kernel_addr_r} - ${fdt_addr_r}

Replace the server IP with the static IP of your server PC. Then compile this script into an u-boot readable image and output it directly to the /srv/tftp directory:

./u-boot/tools/mkimage -A arm64 -O linux -T script \
-C none -a 0x00 -e 0x00 \
-d netboot-rpi3.scr.mkimage \

Make sure that the filename of the .scr file matches with whatever file name you’ve set in the universal .scr script that we’ve prepared further above.

Install a NFS server

The NFS server will serve the root file system to the Raspberry and provide transparent storage.

apt-get install nfs-kernel-server

Edit /etc/exports and add:

/srv/rootfs_rpi3  *(rw,sync,no_root_squash,no_subtree_check,insecure)

To apply the changed ‘exports’ configuration, run

exportfs -rv

Useful to know about the NFS server:

You can restart the NFS server by running service nfs-kernel-server restart

Configuration files are /etc/default/nfs-kernel-server  and /etc/default/nfs-common

Test NFS server

If you want to be sure that the NFS server works correctly, do the following on another PC:

apt-get install nfs-common

Mount the root file system (fix the static IP for your server):

mkdir /tmp/testmount
mount /tmp/testmount
ls -al /tmp/testmount

Install a TFTP server

To install:

apt-get install tftpd-hpa

After installation, check if the TFTP server is running:

ps -ejHf | grep ftp

This command will tell you the default serving directory (/srv/tftp):

/usr/sbin/in.tftpd --listen --user tftp --address --secure /srv/tftp

Here is another command that tells you if the TFTP server is listening:

netstat -l -u | grep ftp

To get help about this server: man tftpd


If you want to be sure that the TFTP server works correctly, do the following on another PC:

apt-get install tftp-hpa

Then see if the server serves the Linux kernel we’ve installed before:

tftp> get linux-rpi3.uImage
tftp> quit

You now should have a local copy of the linux-rpi3.uImage file.


If you’ve done all of the above correctly, you can insert the prepared SD card into your Raspberry Pi and reboot it. The following will happen:

  1. The Raspberry Pi GPU will load the firmware blobs from the SD card.
  2. The firmware blobs will boot the image specified in config.txt. In our case, this is the u-boot binary on the SD card.
  3. The u-boot bootloader will boot.
  4. The u-boot bootloader loads and runs the universal boot.scr script from the SD card.
  5. The boot.scr downloads the specified secondary boot script from the network and runs it.
  6. The secondary boot script …
    • downloads the device tree binary from the network and loads it into memory.
    • downloads the Linux kernel from the network and loads it into memory
    • passes the device tree binary to the kernel, and boots the kernel
  7. the Linux kernel will bring up the ethernet device, connect to the NFS server, and load the regular OS from there.

Many things can go wrong in this rather long sequence, so if you run into trouble, check the Raspberry boot messages output on an attached screen or serial console, and the log files of the NFS and TFTP servers on your server PC.


6 thoughts on “Zero Client: Boot kernel and root filesystem from network with a Raspberry Pi2 or Pi3”

  1. file /etc/systemd/network/ does not exist on my raspbian stretch.

    Do the sdcards need the sdboot sector to be written which is outside of the FAT partition?

  2. Kernel hangup after Starting kernel…

    U-Boot 2018.03-rc2-g50ead631b1-dirty (Mar 23 2018 – 23:50:47 +0530)

  3. Hello Michael,

    I own a couple of raspberry pi 4 B 8GB (server and bare metal client) and would like to ask you if the usage of the SD card on the pi client could be avoided altogether now that network boot is possible by setting BOOT_ORDER=0xf4 in the pi’s EEPROM of the client.

    what would be the additional steps to support loading (via u-boot) an initramfs from the tftp server?

    In fact, I’m looking forward to be using some custom hook scripts which I’ve developed in dracut.

    Thank you.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.