This is a (long, sorry!) follow on from the Debian & Docker on the Seagate Personal Cloud thread. I thought it’d be better to split out the PXE boot side of things and make it a bit more generic as that will undoubtedly be more useful to others thinking about implementing PXE at home themselves.
If you’re not familiar with Preboot eXecution Environment, it’s a way of booting a computer from a network rather than local boot media (e.g., HDD/SSD/CD/DVD/USB/Floppy). It’s been around for a while - I first saw it used on a Novell NetWare, 10BASE2 network a long time ago…
The PXE basic theory of operation is that when a network card makes a request for an IP/subnet mask/gateway/DNS servers via DHCP, it can also receive a TFTP server IP and file name for a client to fetch and then boot from.
I’ve always wanted to have a local PXE server here that served a menu up with various operating systems ready to go. I currently use a fishing tackle box full of USB drives and a CD wallet, but that’s a pain, and I never seem to put them back after I use them. Docker based projects such as netboot.xyz package up this functionality in a really nice way. I don’t have the option of running (easily) on that Seagate Personal Cloud device, but I really wanted to use the Personal Cloud hardware for this task, so I had to put together something to do PXE boot myself. Hopefully the below notes help others understand the theory and perhaps some of the traps I encountered along the way. I don’t intend the below to be a complete tutorial at all, but merely some notes about my own implementation to hopefully assist others. Using something like netboot.xyz is almost certainly going to do a better job of this than I did!
My goals with this were:
- Have a physical switch port at my workbench that served files over PXE to whatever client was plugged into it
- Implemented on a physically separate device rather than a VM
- Totally isolated from my home network (as I am occasionally working on friend/family members’ malware ridden machines to help them out)
- Does not interfere with the existing network in any way
- Serve 32 bit, 64 bit, BIOS, and UEFI clients as universally as possible
- Some sort of a menu, so that I don’t have to change a single boot file entry in a configuration file each time I want to boot something different
- Serve Windows and Windows based (i.e., WinPE) and *nix based operating systems
Optionally:
- Serve ARM64 clients (out of scope for this time around, but I am planning to come back to this)
- Avoid having to pull the Seagate Personal Cloud apart again once it’s back together
- Files are locally hosted, with the option to boot files on the fly from the internet
Those goals led into the way I set things up:
- Debian 13 on the Personal Cloud (see the other thread)
- Using a 60GB SSD rather than the one in that thread (as it was a good way to recycle a SSD in the parts bin and still gave me space to serve 10-30 ISOs of various sizes)
- The Personal Cloud itself was to request a DHCP address (so I didn’t have to worry about fixed IPs - if my network architecture changes, the ‘PXE server’ effortlessly changes with it)
- All the PXE magic happens on a separate VLAN and subnet - gives me the physical switch port in my homelab I want, and isolates things from the rest of the network
- iPXE is used as a jump start to boot via TFTP, and it can then load ISOs over HTTP
- IPv4 only, and files served over HTTP only (not HTTPS) to simplify implementation
- DHCP server functions are handled via my existing network infrastructure, not by the Personal Cloud
Earlier this week I set up a separate VLAN and subnet, in my case 10.7.93.0/29 (7, 9, 3 mapping to P, X, E on a telephone keypad - I can easily identify what that traffic is!). My MikroTik router issues a static DHCP lease to the Personal Cloud now called pxeboot of 10.7.93.2 - more on DHCP later in the post. I won’t go into the VLAN config here, but the requisite ports were set up to give me a dedicated port on my homelab switch for PXE and also allow traffic tagged with 793 to go to that network (e.g., for Proxmox VMs).
The pxeboot device runs tftpd-hpa and lighttpd, both of which have armhf packages in Debian 13. I opted for lighttpd because I want to try and keep overhead really low on that ARMv7 device, and because php is still available if I want to experiment with delivering some more advanced menus via PXE later. Something like Caddy or any other HTTP server would work just fine too.
iPXE can chainload, where a small file is served via PXE to load iPXE, and then iPXE can perform more advanced functionality (e.g., pull ISOs over HTTP). iPXE seems like one of those amazing projects which is infinitely flexible. I was setting up to compile my own versions, but they already provide undionly.kpxe for BIOS machines and ipxe.uefi for UEFI machines which were sufficient for what I wanted to achieve. Both of these files were placed in /srv/tftp.
I’ll skip straight to the last step - serving the files over HTTP as I’m sure you’ll all be familiar with that step. I configured lighttpd to allow directory listings, and dumped my ISOs into onto the pxeboot machine to be served. The ISOs are accessible at http://10.7.93.2/<filename>. I also created an ipxe directory which I’ll discuss shortly.
TFTP server set up
, HTTP server set up
. The last pieces of the puzzle are configuring DHCP and setting up a menu for iPXE to display.
DHCP options 66 and 67 serve the IP for the TFTP server and the boot file name to retrieve, respectively. In this case, option 66 is pretty easy 10.7.93.2. Option 67 is where things get slightly more complicated.
I want to serve one file to BIOS clients, one to UEFI clients, and a third to iPXE clients. Many DHCP servers can modify their responses based on the client’s request. There’s plenty of documentation on how to do this using various DHCP servers, so I’ll keep this relatively generic. Essentially we want a way to identify whether the “device” making the request at the DHCP Discover & DHCP Request stages of the transaction booted via BIOS, UEFI, or is the iPXE firmware.
Big disclaimer: this is how I implemented it after looking through a ton of resources online (and troubleshooting with Wireshark) but it’s almost certainly not the optimal way. I had to work around a few MikroTik and UEFI quirks to get this working.
Working backwards, iPXE clients are relatively easy to identify, as they send DHCP Option 77 (User Class) as the string “iPXE”. The DHCP server can be configured to reply with DHCP Option 67 of a URI rather than a file to retrieve via TFTP, as iPXE understands what to do with that.
BIOS clients are identified by them either sending DHCP Option 93 (Client System Architecture) as 0, or not sending it all (because they pre date that option existing). These clients get undionly.kpxe, which is the iPXE BIOS firmware. The iPXE firmware then boots, and makes a DHCP request of its own, which is identified as iPXE and served a URI, as above.
BIOS and iPXE clients worked straight away - very cool!
UEFI was a giant pain in the butt, and where I spent most of my day. I’ve got no doubt that recent versions of UEFI are well behaved (and in fact can apparently boot from URIs too - untested by me), but I’m trying to implement something as close to universal as possible, so I’ve got to handle a few bugs. In theory, UEFI clients should send DHCP Option 93 with a value of 7. I couldn’t get my MikroTik router to reliably work with that, so I opted to match based on DHCP Option 60 (Vendor Class Identifier) which is a string sent by the client. I had odd issues with that too, I had to change to matching based on the hex for Arch:00007 which appears in that string for UEFI clients. For anyone in MikroTik land playing at home - it would match based on the hex and not on the string. The full command I used was /ip dhcp-server matcher add address-pool=pxeboot code=60 matching-type=substring name=UEFI-client option-set=pxe-uefi server=pxeboot value=0x417263683a3030303037 where 0x417263683a3030303037 = Arch:00007. I’ve got no idea if this is a bug in RouterOS or not, but for whatever reason the string would never match but the hex would
.
UEFI clients were now being reliably identified, but again I ran into some quirks with the way some test machines implemented UEFI. Some clients silently ignore UEFI file names and only boot from bootx64.efi. I’ve run into this before a few times, as the Wyse 3040s I use on some other projects (details in other threads) have this annoying quirk. I renamed ipxe.efi to bootx64.efi (+ changed the DHCP Option 67 value for UEFI clients) and some clients that weren’t working started to work. Probably could have symlinked it, but oh well. More UEFI clients were working, but not all.
Back to Wireshark for a closer look. The UEFI client that were failing to boot were requesting bootx64.efi (which is really ipxe.efi, just renamed) from the TFTP server with an additional something at the end.
Very long story short, this is apparently a bug in some implementations of UEFI rather than a MikroTik issue. From what I understand, the MikroTik is strictly following the standard, but these clients are interpreting the terminator at the end of the DHCP Offer as a part of the boot file name as that’s the last thing being sent. Looks like my options were to send something after Option 67 (making sure it’s not the last thing in the DHCP Offer), rename/symlink the file to include the 0xFF at the end, or add a null terminator at the end of DHCP Option 67 for UEFI clients. I went for the latter, and bootx64.efi became 0x626f6f747836342e65666900 (full command /ip dhcp-server optionadd code=67 name=uefi-pxe-bootfile value=0x626f6f747836342e65666900). Success! UEFI clients boot iPXE, and then iPXE makes its own DHCP request and gets a URI to boot.
The last piece of the puzzle is the iPXE menu. I’ve stated several times that iPXE clients (identified via DHCP Option 77) are getting served a URI. In my case, it’s literally a DHCP Option 67 (boot file) of http://10.7.93.2/ipxe/menu.ipxe
The contents of this file are below:
#!ipxe
menu iPXE ISO Boot Menu
item debian Boot Debian 13.1 amd64 Netinst ISO
item kali Boot Kali 2025.2 Netinst ISO
item windows10_64 Boot Windows 10 64-bit Installer ISO
item windows10_32 Boot Windows 10 32-bit Installer ISO
item ubcd Boot Ultimate Boot CD 5.3.9
item shell iPXE shell
item reboot Reboot
choose --default ubcd --timeout 10000 target && goto ${target}
:debian
sanboot --no-describe http://10.7.93.2/debian-13.1.0-amd64-netinst.iso || goto failed
:kali
sanboot --no-describe http://10.7.93.2/kali-linux-2025.2-installer-netinst-amd64.iso || goto failed
:windows10_64
sanboot --no-describe http://10.7.93.2/Win10_22H2_English_x64v1.iso || goto failed
:windows10_32
sanboot --no-describe http://10.7.93.2/Win10_22H2_English_x32v1.iso || goto failed
:ubcd
sanboot --no-describe http://10.7.93.2/ubcd539.iso || goto failed
:shell
shell
:reboot
reboot
:failed
echo Boot failed, returning to menu...
sleep 3
goto start
Most of the file is probably relatively straightforward to the HLB community, but I’ll point out sanboot which tells iPXE to boot from a SAN/iSCSI/HTTP target. In my case, it pulls the ISOs being served via lighttpd as discussed above.
The menu comes up on the client:
Tested successfully on VMs, and real UEFI and BIOS hardware of varying quality. The default option is to boot into Ultimate Boot CD after 10 seconds. I’ll probably reorder it to the top when I add a few other options. Both 64 and 32 bit Windows boot, as do Debian and Kali. I’m planning to add a few other ISOs to the menu as I begin to use it more.
Finally, I’ll flag the iPXE shell option on the bottom. That will drop the user into the iPXE shell, where commands such as chain and boot can be issued. One such use would be to boot from any http source on the internet (chain/boot in iPXE documentation).
For the sake of documentation completeness, I’ve been dropping ISOs to the pxeboot unit either via direct wget download, or using SCP from my MacBook. I toyed with setting up Samba, but decided against it as I want to keep the overhead low on the Personal Cloud and the ISOs are going to remain relatively static. Similarly, I haven’t bothered trying to set up any Web UI to add menu entries (like netboot.xyz’s functionality) as hand editing it won’t be a huge imposition for how infrequently it will change.
Still very much a work in progress, but hopefully that helps others and perhaps inspires people to give PXE a go in their own homelab. I will admit, during testing this afternoon it was very cool to plug any device into that port on the switch and be presented with the menu!
Edit 12/9: Tested a Linux and Windows install this morning and both failed early into the process. sanboot looks like it’s not the right way to do this - I’ll come back to the thread and update it once I get the menu side of things sorted out.




