Pangram verdict · v3.3
We believe that this document is fully human-written
AI likelihood · overall
HumanArticle text · 1,537 words · 8 segments analyzed
The historic go-to solution for network booting is PXE. PXE is based on DHCP and TFTP. It is tricky to correctly configure, even trickier to make it highly available and good luck with the security with this clear-text unsigned protocol.The modern web has long standardized on HTTPS with TLS certificates for server authentication, integrity and confidentiality. Moreover, highly available setups are a solved problem when it comes to HTTPS. Even better, the encryption layer makes it practical to boot over the Internet without immediately facing the threat of a man-in-the-middle attack that would be trivial with TFTP (remember, the leading t stands for “trivial”, not “secure”).The good news is, most modern UEFI-based system support booting over HTTP(S).In this post, we’ll boot the snponly variant of netboot.xyz directly from the official website. Be prepared for some fun with HTTPS.All these tests were performed on Ubuntu 26.04 with the provided 1:10.2.1+ds-1ubuntu3 Qemu and 2025.11-3ubuntu7 OVMF packages, unless otherwise stated. Note that, for reasons that will become clear later in this post, older versions might actually work better 🙃.Starting with the simple case: HTTP boot discovered over DHCP(Righteously) Suspecting that the HTTPS variant would be a tough beast to beat, I started this journey with a first test that side steps the certificate trust and other quirks at the beginning.The URL for the boot firmware is: http://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi.The aim here is to demonstrate a minimal setup to make it easier to integrate it in your own environment. We’ll use a non-root Qemu machine with userland-based SLIRP networking and no additional devices like storage for instance. The whole system will run in-memory.Let’s start with a first iteration based on:The OVMF firmware, without secure boot to keep things simpleA network card that will emulate a DHCP server and point the UEFI to the HTTP boot targetOutput on the console1qemu-system-x86_64 \ 2
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \ 3 -nic user,bootfile=http://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi \ 4 -nographic This is promising, but it fails to boot over the network:1BdsDxe: No bootable option or device was found. 2BdsDxe: Press any key to enter the Boot Manager Menu. This is because the network stack in the OVMF requires a random number generator device to work. When you think about it, this is almost obvious. Pretty much each layer of the network stack requires randomness, from Ethernet collision avoidance to TLS through DHCP itself.This can be enabled very easily by adding the following flag to the Qemu command line:1-device virtio-rng-pci Any other configuration change that would provide a random number generator would work equally well. For example, one could enable KVM and use -cpu host which would grant access to the CPU’s random number generation instructions.While somewhat obvious, this is pretty hard to figure out without help because of the lack of error logs. In this case, I was helped by asking the free version of Claude.I was wondering how one could figure this out by induction/deduction rather than brute-forcing it with a (very helpful) LLM. It turns out the dependency is declared in the [Depex] section of NetworkPkg/Library/DxeNetLib/DxeNetLib.inf:1[Depex] 2 gEfiRngProtocolGuid Under the hood, this pushes the GUID of the EFI random number generation protocol on the dependency stack so that any EFI package linking against the DxeNetLib will implicitly require it when the dispatcher evaluates the dependencies at runtime.For future reference (to myself), one could work out the dependency at runtime from the debug logs by enabling the “DEBUG_DISPATCH” flag of gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel in OvmfPkg/OvmfPkgX64.dsc.
Be prepared for a world of GUID based debugging!(If you are frustrated by the brute-force approach and would rather see a log-based approach, stay tuned for the HTTPS part.)With this in place, tadaa! 1>>Start PXE over IPv4. 2 Station IP address is 10.0.2.15 3 4 Server IP address is 10.0.2.2 5 NBP filename is http://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi 6 NBP filesize is 0 Bytes 7 PXE-E23: Client received TFTP error from server. 8BdsDxe: failed to load Boot0002 "UEFI PXEv4 (MAC:525400123456)" from PciRoot(0x0)/Pci(0x3,0x0)/MAC(525400123456,0x1)/IPv4(0.0.0.0,0x0,DHCP,0.0.0.0,0.0.0.0,0.0.0.0): Not Found 9 10>>Start PXE over IPv6. 11 PXE-E16: No valid offer received. 12BdsDxe: failed to load Boot0003 "UEFI PXEv6 (MAC:525400123456)" from PciRoot(0x0)/Pci(0x3,0x0)/MAC(525400123456,0x1)/IPv6(0000:0000:0000:0000:0000:0000:0000:0000,0
x0,Static,0000:0000:0000:0000:0000:0000:0000:0000,0x40,0000:0000:0000:0000:0000:0000:0000:0000): Not Found 13 14>>Start HTTP Boot over IPv4.... 15 Station IP address is 10.0.2.15 16 17 URI: http://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi 18 File Size: 310784 Bytes 19 Downloading...100%BdsDxe: loading Boot0004 "UEFI HTTPv4 (MAC:525400123456)" from PciRoot(0x0)/Pci(0x3,0x0)/MAC(525400123456,0x1)/IPv4(0.0.0.0,0x0,DHCP,0.0.0.0,0.0.0.0,0.0.0.0)/Uri() 20BdsDxe: starting Boot0004 "UEFI HTTPv4 (MAC:525400123456)" from PciRoot(0x0)/Pci(0x3,0x0)/MAC(525400123456,0x1)/IPv4(0.0.0.0,0x0,DHCP,0.0.0.0,0.0.0.0,0.0.0.0)/Uri() 21iPXE initialising
devices... 22autoexec.ipxe... ok 23 24 25 26iPXE 2.0.0+ (g36e8c) -- Open Source Network Boot Firmware -- https://ipxe.org 27Features: DNS HTTP HTTPS iSCSI NFS TFTP VLAN AoE EFI Menu 28netboot.xyz - v3.x 29Hit the m key to open failsafe menu... Did I mention that reaching this point takes ~1m 15s? 🐌Speeding up HTTP boot, through configurationOne minute just to reach the one boot attempt we are interested in is horribly long and not anywhere near an acceptable target. Now that it works, we can try to make it fast. The good news is that it is relatively easy.The UEFI network stack sequentially tries:IPv4 PXE: The IPv4 part works, but the HTTP URL is invalid, a TFTP reference is expected.IPv6 PXE: Nothing works as no IPv6 is configured.IPv4 HTTP: This finally works.IPv6 HTTP: This would have been the following attempt.Ideally, we would need a mechanism to instruct the OVMF to avoid losing time on the legacy PXE stuff.Fortunately, Qemu has a way to pass options to the firmware using the -fw_cfg flag and (some) documentation of the available OVMF settings is even available here: https://github.com/tianocore/edk2/blob/master/OvmfPkg/RUNTIME_CONFIG.mdFrom the documentation, we can find good candidates:opt/org.tianocore/IPv4PXESupportopt/org.tianocore/IPv6PXESupportWe can even see etc/edk2/https/cacerts. This might come in handy for the next part. Who knows?Wiring it all together, the final Qemu command line for HTTP boot looks like:1qemu-system-x86_64 \ 2 -device virtio-rng-pci \ 3 -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \ 4 -nic
user,bootfile=http://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi \ 5 -fw_cfg name=opt/org.tianocore/IPv4PXESupport,string=no \ 6 -fw_cfg name=opt/org.tianocore/IPv6PXESupport,string=no \ 7 -nographic This will boot to netboot.xyz in roughly 5 seconds, which is far more acceptable.Another way to HTTP boot, through UEFI variablesWe have reached a point that works but is hard to transpose to real hardware. On real hardware, we do not have these convenient OVMF runtime tunables. What we have instead are UEFI variables. Could we achieve the same replacing Qemu trickery with UEFI variables? Ideally, we would need to pre-generate the variables before the first start of the VM to reach parity with the previous approach.(OK, OK, the real reason for this alternative is to smooth the path a bit to the HTTPS variant.)Ideally, we’d place the variables in a human-readable configuration file would be the dream solution. Wait, did I say human-readable? Yes, Qemu has it since 10.0 and we have 10.2.1. See https://github.com/tianocore/edk2/blob/master/OvmfPkg/QEMU_PV_VARS.md.Of course, the option is not enabled in the OVMF build in Ubuntu and I do not want to rebuild the OVMF. That’s too much of a hassle. (Remember, we are only at the easy HTTP step).Fortunately, virt-fw-vars from https://gitlab.com/kraxel/virt-firmware can inject a “Next Boot URI” directly into an OVMF_VARS_4M.fd EFI variable store: 1# Install firmware tools 2sudo apt install python3-virt-firmware 3 4# Inject the "Next Boot entry" in a fresh variable store 5virt-fw-vars --input /usr/share/OVMF/OVMF_VARS_4M.fd --set-boot-uri
http://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi --output ./OVMF_VARS_4M.fd 6 7# Boot the VM 8qemu-system-x86_64 \ 9 -device virtio-rng-pci \ 10 -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \ 11 -drive if=pflash,format=raw,file=./OVMF_VARS_4M.fd \ 12 -nic user \ 13 -nographic With this, we can boot a VM over HTTP with two methods:The first, using DHCP bootfile.The second by crafting the next boot UEFI variable.Ready for adding the “S” in HTTPS?UEFI HTTPS boot: Merely a matter of adding an “s”?Surely, this is as simple as replacing “http://” with “https://” in at least one of the variants?Not quite.The DHCP/bootfile-based approach fails after a horribly long ~1m timeout with the following message:1>>Start HTTP Boot over IPv4..... 2 Error: Could not retrieve NBP file size from HTTP server. 3 4 Error: Server response timeout. 5BdsDxe: failed to load Boot0004 "UEFI HTTPv4 (MAC:525400123456)" from PciRoot(0x0)/Pci(0x3,0x0)/MAC(525400123456,0x1)/IPv4(0.0.0.0,0x0,DHCP,0.0.0.0,0.0.0.0,0.0.0.0)/Uri(): Not Found At least the var store variant is
faster to fail, roughly 5s:1>>Start HTTP Boot over IPv4.... 2 Station IP address is 10.0.2.15 3 4 URI: https://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi 5 6 Error: Could not retrieve NBP file size from HTTP server. 7 8 Error: Unexpected network error. None of these error messages are actionable. The only valuable hint here is that the var store variant at least succeeds in getting an IP address and finding the next boot URL. Regarding the failure of the DHCP-based approach, I honestly have no clue why it failed.This will clearly not only be a matter of adding the “s” in “https”.The hunt for logsEDK II/OVMF firmware conveniently produce debug logs on a specific virtual serial port. Fortunately, Gerd Hoffmann (incidentally also the author/maintainer of virt-fw-vars we used earlier) recently documented the correct incantation on his blog: https://www.kraxel.org/blog/2025/10/firmware-logging/.Let’s do this, add -device isa-debugcon,iobase=0x402,chardev=fw -chardev file,id=fw,path=debug.log to the Qemu command line and check the logs.Here is the full output:Yes, you read it correctly. There is no mistake. There is nothing. At all.This is because this only works with DEBUG builds of OVMF and we have a RELEASE build. This makes sense, but Ubuntu does not ship such a build. We have no choice but to rebuild it ourselves.For this, I took the easy path. I fetched the package sources and re-used the debian/rules Makefile. Deb packages can be a painful beast, but building OVMF correctly is also an interesting experience.