Learning to boot from PXE · Imran’s Blog

Posted on

I bought a new laptop, GPD Pocket 4. It came with Windows installed by default, and I wanted to install Nix on it.

I grabbed a USB, ddCreated Nixos ISO image on it and tried to boot. The laptop did not recognize the drive. Turns out, the drive is corrupted, no computer will turn it off.

The normal thing would have been to just get a new USB and install it and start setting up the laptop. This means I either have to go out or wait for a new USB to arrive. I don’t want to go out and wait for my laptop to setup. I have free time right now and I have no idea when I’ll next have free time.

There were two other boot options in the menu. Something about PXE on IPv4 or IPv6. I only knew that PXE allows network boot. So come on, use this time to learn something new.

#dhcp

As I’ve learned, the first part of this process is DHCP. When a device connects to the network it sends the message “Hey give me an IP” (I don’t really know how it works and I didn’t bother to look it up). Then your DHCP service sees this message and responds with the IP. Clients and servers can set “options” on these requests that may send additional information as part of these requests. I don’t know what the client sets first, but I know the server needs to set the boot file name and location of the TFTP server. TFTP is kind of like FTP.

PXE reads a boot file (usually something like .pxe) from the TFTP server and then executes its code. Other boot files are retrieved from the TFTP server as needed.

While learning it, people on the internet don’t seem too fond of TFTP, saying it can be slow. There exists iPXE which is considered an improved PXE. PXE (like Bioassays), are manufacturer specific and are not created equal. iPXE strives to be better and supports many more things (like booting from ISO, and talking in HTTP). So if all this goes well I’ll fire up IPXE, point it at the ISO I’ve already downloaded and I’m off to the races!

Spoiler alert, I didn’t make it to the race.

To run iPXE, the iPXE.pxe executable needs to be served by TFTP. I’m running an OpenSense box for my router/firewall and it has enough disk space and RAM that I should be able to process it all. It’s quite easy to set up the DHCP stuff through the UI. The IPXE client sets a client option on its DHCP requests, so you’ll want to create a tag in OpenSense from its user-class (IPXE) and respond with the http server’s DHCP boot (what the tab is called in the UI) value.

The flow should be:

PXE -> gets TFTP address -> download and run iPXE -> gets HTTP address -> has iPXE run our ISO

DHCP stuff can be done through the UI so that was it. The TFTP stuff was not available on the web UI so it had to be done via SSH.

#TFTP

This was my first time venturing into a BSD box. After this whole process I found (Free)BSD to be strangely comfortable. I can’t explain how or why, but it’s obvious. Login prompt, simple shell prompt (csh?), man pages, disk layout, programs from OpenSense. Like even though I didn’t have access to all the newer versions of the tools (nvim/rg vs vim/grep), I still got what I wanted and it just felt lovely and comfortable.

Anyway, OPNsense ships with dnsmasq and dnsmasq can also act as a TFTP server. I found this while trying to search for a TFTP program to install via the UI. I don’t know how to enable it, nor did I want to look at it (via the Internet), so I just read the man page.

man dnsmasq

Reading the man pages was a pleasure (or maybe it was my first time reading something from volume 8). It told me exactly what the program can do and how to configure it (just searched tftp). conf files were listed at the bottom, first /etc/dnsmasq.conf which was not present on my system yet /usr/local/etc/dnsmasq.conf Did.

The first line of that file warns you not to manually edit the file and below you see the conf-dir option set /usr/local/etc/dnsmasq.conf.d
I saw a README in that conf dir and, doing a cat resulted in this message:

cat /usr/local/etc/dnsmasq.conf.d/README
# Dnsmasq plugin directory:
# Add your *.conf files here, read in alphabetical order

ok sure why don’t we do that

vim /usr/local/etc/dnsmasq.conf.d/10-tftp.conf
enable-tftp
tftp-root=/srv/tftp
:x
mkdir -p /srv/tftp
fetch -r https://boot.ipxe.org/ipxe.efi -o /srv/tftp/

I used the web UI to restart dnsmasq, but you can also use configctl Doing it through shell. Now when I boot the laptop I see that it loads iPXE but then fails because the http server does not exist. This is progress though, now we just need to serve our ISO over http.

One thing to note is that almost all the instructions online are focused on legacy/bios boot. All my devices boot via UEFI (which is why we downloaded EFI above instead of the .kpxe file). There are ways to setup DHCP to respond with the appropriate files for both UEFI or BIOS boot, but I don’t care much about that. There are other things that try to simplify this whole process like pixieboot and netboot.xyz but I’m not interested in them.

# HTTP

OpenSense runs LightTPD to serve its web UI and I would like to turn it off for IPXE content.

The hardest part here was figuring out the web UI configuration /usr/local/etc/lighttpd_webgui/ through psI had to disable the SSL redirect option from the web UI and add it myself at the end of the conf file instead, because of how the conf is loaded, I couldn’t think of a different way to disable 443 port redirection only for IPXE paths,

cat /usr/local/etc/lighttpd_webgui/conf.d/00-ipxe.conf
# Serve /srv/tftp under http:///ipxe/
alias.url += ( "/ipxe/" => "/srv/tftp/" )
url.redirect += ( "^/ipxe$" => "/ipxe/" )

$SERVER["socket"] == "0.0.0.0:80" {
    ssl.engine = "disable"
    $HTTP["url"] !~ "^/ipxe(?:/|$)" {
        $HTTP["host"] =~ "(.*)" {
            url.redirect = ( "^/(.*)" => "https://%1/$1" )
        }
    }
}

$SERVER["socket"] == "[::]:80" {
    ssl.engine = "disable"
    $HTTP["url"] !~ "^/ipxe(?:/|$)" {
        $HTTP["host"] =~ "(.*)" {
            url.redirect = ( "^/(.*)" => "https://%1/$1" )
        }
    }
}

I started with a basic boot.ixpe file

#!ipxe
menu Choose an ISO
item nix-minmal NixOS 25.04 Minimal
item nix-gui   NixOS 25.04 GUI
choose target && goto ${target}

:nix-minimal
sanboot http://10.0.0.1/ipxe/nixos-minimal-25.05.812242.3de8f8d73e35-x86_64-linux.iso
goto menu

:nix-gui
sanboot http://10.0.0.1/ipxe/nixos-graphical-25.05.812242.3de8f8d73e35-x86_64-linux.iso
goto menu

And here’s what I screwed up at first, it didn’t work.

I’ll get a boot but then Nixos will complain /mnt/iso Or something is missing and failing to move forward.

This discussion has better information as to why this doesn’t work: https://github.com/ipxe/ipxe/discussions/962

# Proper netboot files

So my dream of network booting from an ISO is shattered, where do I go from here?

Well it turns out that the ISO comes with a bootloader, which includes instructions for booting the kernel with the initial RAM disk (prompted when I learned initrd Meaning). So can’t we do this too? The answer is yes! (Or so I think). I did not attempt to extract the files from the ISO, but used Nix’s built-in Netboot image generator which creates the necessary files.

I had to make changes to the generated .ixpe file to include only the http URL but in the end everything worked fine.

cat netboot.ipxe
#!ipxe
# Use the cmdline variable to allow the user to specify custom kernel params
# when chainloading this script from other iPXE scripts like netboot.xyz
kernel http://10.0.0.1/ipxe/bzImage init=/nix/store/hrgkskx4jqdz4nl3p1f4m1dvrr9b3lij-nixos-system-nixos-kexec-25.11pre708350.gfedcba/init initrd=initrd nohibernate loglevel=4 lsm=landlock,yama,bpf ${cmdline}
initrd http://10.0.0.1/ipxe/initrd
boot

I still wonder if I can extract the files from the graphical installer and boot KDE over the network, but now that the OS is installed I’m less interested. maybe one day i’ll come again



Leave a Comment