Tutorial

nobodd is a confusingly named, but simple TFTP server intended for net-booting Raspberry Pis directly from OS images without having to loop-back mount or otherwise re-write those images.

In order to get started you will need the following pre-requisites:

  • A Raspberry Pi you wish to netboot. This tutorial will be assuming a Pi 4, but the Pi 2B, 3B, 3B+, 4B, and 5 all support netboot. However, all have subtly different means of configuring their netboot support, so in the interests of brevity this tutorial will only cover the method for the Pi 4.

  • A micro-SD card. This is only required for the initial netboot configuration of the Pi 4, and for discovering the serial number of the board.

  • A server that will serve the OS image to be netbooted. This can be another Raspberry Pi, but if you eventually wish to scale to several netbooting clients you probably want something with a lot more I/O bandwidth. We will assume this server is running Ubuntu 24.04, and you have root authority to install new packages.

  • Ethernet networking connecting the two machines; netboot will not operate over WiFi.

  • The addressing details of your ethernet network, specifically the network address and mask (e.g. 192.168.1.0/24).

Client Side

To configure your Pi 4 for netboot, use rpi-imager to flash Ubuntu Server 24.04 64-bit to your micro-SD card. Boot your Pi 4 with the micro-SD card and wait for cloud-init to finish the initial user configuration. Log in with the default user (username “ubuntu”, password “ubuntu”, unless you specified otherwise in rpi-imager), and follow the prompts to set a new password.

Run sudo rpi-eeprom-config --edit, and enter your password for “sudo”. You will find yourself in an editor, with the Pi’s boot configuration from the EEPROM, which will most likely look something like the following:

[all]
BOOT_UART=0
WAKE_ON_GPIO=1
ENABLE_SELF_UPDATE=1
BOOT_ORDER=0xf41

Note

Do not be concerned if several other values appear, or the ordering differs. Various versions of the Raspberry Pi boot EEPROM have had differing defaults for their configuration, and some later ones include a lot more values.

The value we are concerned with is BOOT_ORDER under the [all] section, which may be the only section in the file. This is a hexadecimal value (indicated by the “0x” prefix) in which each digit specifies another boot source in reverse order. The digits that may be specified include:

#

Mode

Description

1

SD CARD

Boot from the SD card

2

NETWORK

Boot from TFTP over ethernet

4

USB-MSD

Boot from a USB MSD

e

STOP

Stop the boot and display an error pattern

f

RESTART

Restart the boot from the first mode

A full listing of valid digits can be found in the Raspberry Pi documentation. The current setting shown above is “0xf41”. Remembering that this is in reversed order, we can interpret this as “try the SD card first (1), then try a USB mass storage device (4), then restart the sequence if neither worked (f)”.

We’d like to try network booting first, so we need to add the value 2 to the end, giving us: “0xf412”. Change the “BOOT_ORDER” value to this, save and exit the editor.

Warning

You may be tempted to remove values from the boot order to avoid delay (e.g. testing for the presence of an SD card). However, you are strongly advised to leave the value 1 (SD card booting) somewhere in your boot order to permit recovery from an SD card (or future re-configuration).

Upon exiting, the rpi-eeprom-config command should prompt you that you need to reboot in order to flash the new configuration onto the boot EEPROM. Enter sudo reboot to do so, and let the boot complete fully.

Once you are back at a login prompt, log back in with your username and password, and then run sudo rpi-eeprom-config once more to query the boot configuration and make sure your change has taken effect. It should output something like:

[all]
BOOT_UART=0
WAKE_ON_GPIO=1
ENABLE_SELF_UPDATE=1
BOOT_ORDER=0xf412

Finally, we need the serial number of your Raspberry Pi. This can be found with the following command.

$ grep ^Serial /proc/cpuinfo
Serial          : 10000000abcd1234

Note this number down somewhere safe as we’ll need it for the server configuration later. The Raspberry Pi side of the configuration is now complete, and we can move on to configuring our netboot server.

Server Side

As mentioned in the pre-requisites, we will assume the server is running Ubuntu 24.04, and that you are logged in with a user that has root authority (via “sudo”). Firstly, install the packages which will provide our TFTP, NBD, and DHCP proxy servers, along with some tooling to customize images.

$ sudo apt install nobodd-tftpd nobodd-tools nbd-server xz-utils dnsmasq

The first thing to do is configure dnsmasq(8) as a DHCP proxy server. Find the interface name of your server’s primary ethernet interface (the one that will talk to the same network as the Raspberry Pi) within the output of the ip addr show up command. It will probably look something like “enp2s0f0”.

$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
        valid_lft forever preferred_lft forever
2: enp2s0f0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 0a:0b:0c:0d:0e:0f brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.4/16 brd 192.168.1.255 scope global enp2s0f0
       valid_lft forever preferred_lft forever
    inet6 fd00:abcd:1234::4/128 scope global noprefixroute
       valid_lft forever preferred_lft 53017sec
    inet6 fe80::beef:face:d00d:1234/64 scope link
        valid_lft forever preferred_lft forever
3: enp1s0f1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP group default qlen 1000
    link/ether 1a:0b:0c:0d:0e:0f brd ff:ff:ff:ff:ff:ff
4: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 02:6c:fc:6f:56:5c brd ff:ff:ff:ff:ff:ff
    inet6 fe80::60d9:48ff:fee3:c955/64 scope link
       valid_lft forever preferred_lft forever
...

Add the following configuration lines to /etc/dnsmasq.conf adjusting the ethernet interface name, and the network mask on the highlighted lines to your particular setup.

# Only listen on the primary ethernet interface
interface=enp2s0f0
bind-interfaces

# Perform DHCP proxying on the network, and advertise our
# PXE-ish boot service
dhcp-range=192.168.1.255,proxy
pxe-service=0,"Raspberry Pi Boot"

Restart dnsmasq to ensure it’s listening for DHCP connections (unfortunately reload is not sufficient in this case).

$ sudo systemctl restart dnsmasq.service

Next, we need to obtain an image to boot on our Raspberry Pi. We’ll be using the Ubuntu 24.04 Server for Raspberry Pi image as this is configured for NBD boot out of the box. We will place this image under a /srv/images directory and unpack it so we can manipulate it.

$ sudo mkdir /srv/images
$ sudo chown ubuntu:ubuntu /srv/images
$ cd /srv/images
$ wget http://cdimage.ubuntu.com/releases/24.04/release/ubuntu-24.04-preinstalled-server-arm64+raspi.img.xz
 ...
$ wget http://cdimage.ubuntu.com/releases/24.04/release/SHA256SUMS
 ...
$ sha256sum --check --ignore-missing SHA256SUMS
$ rm SHA256SUMS
$ unxz ubuntu-24.04-preinstalled-server-arm64+raspi.img.xz

We’ll use the nobodd-prep command to adjust the image so that the kernel will try and find its root on our NBD server. At the same time, we’ll have the utility generate the appropriate configurations for nbd-server(1) and nobodd-tftpd.

nobodd-prep needs to know several things in order to operate, but tries to use sensible defaults where it can:

  • The filename of the image to customize; we’ll simply provide this on the command line.

  • The size we want to expand the image to; this will be size of the “disk” (or “SD card”) that the Raspberry Pi sees. The default is 16GB, which is fine for our purposes here.

  • The number of the boot partition within the image; the default is the first FAT partition, which is fine in this case.

  • The name of the file containing the kernel command line on the boot partition; the default is cmdline.txt which is correct for the Ubuntu images.

  • The number of the root partition within the image; the default is the first non-FAT partition, which is also fine here.

  • The host-name of the server; the default is the output of hostname --fqdn but this can be specified manually with nobodd-prep --nbd-host.

  • The name of the NBD share; the default is the stem of the image filename (the filename without its extensions) which in this case would be ubuntu-24.04-preinstalled-server-arm64+raspi. That’s a bit of a mouthful so we’ll override it with nobodd-prep --nbd-name.

  • The serial number of the Raspberry Pi; there is no default for this, so we’ll provide it with nobodd-prep --serial.

  • The path to write the two configuration files we want to produce; we’ll specify these manually with nobodd-prep --tftpd-conf and nobodd-prep --nbd-conf

Putting all this together we run,

$ nobodd-prep --nbd-name ubuntu-noble --serial 10000000abcd1234 \
> --tftpd-conf tftpd-noble.conf --nbd-conf nbd-noble.conf \
> ubuntu-24.04-preinstalled-server-arm64+raspi.img

Now we need to move the generated configuration files to their correct locations and ensure they’re owned by root (so unprivileged users cannot modify them), ensure the modified image is owned by the “nbd” user (so the NBD service can read and write to it), and reload the configuration in the relevant services.

$ sudo chown nbd:nbd ubuntu-24.04-preinstalled-server-arm64+raspi.img
$ sudo chown root:root tftpd-noble.conf nbd-noble.conf
$ sudo mv tftpd-noble.conf /etc/nobodd/conf.d/
$ sudo mv nbd-noble.conf /etc/nbd-server/conf.d/
$ sudo systemctl reload nobodd-tftpd.service
$ sudo systemctl reload nbd-server.service

Testing and Troubleshooting

At this point your configuration should be ready to test. Ensure there is no SD card in the slot, and power it on. After a short delay you should see the “rainbow” boot screen appear. This will be followed by an uncharacteristically long delay on that screen. The reason is that your Pi is transferring the initramfs over TFTP which is not the most efficient protocol [1]. However, eventually you should be greeted by the typical Linux kernel log scrolling by, and reach a typical booted state the same as you would with a freshly flashed SD card.

If you hit any snags here, the following things are worth checking:

  • Pay attention to any errors shown on the Pi’s bootloader screen. In particular, you should be able to see the Pi obtaining an IP address via DHCP and various TFTP request attempts.

  • Run journalctl -f --unit nobodd-tftpd.service on your server to follow the TFTP log output. Again, if things are working, you should be seeing several TFTP requests here. If you see nothing, double check the network mask is specified correctly in the dnsmasq(8) configuration, and that any firewall on your server is permitting inbound traffic to port 69 (the default TFTP port).

  • You will see numerous “Early terminate” TFTP errors in the journal output. This is normal, and appears to be how the Pi’s bootloader operates [2].