This guide will walk you through setting up the iPXE PXE boot server on your Debian 13 VM.
- Fresh Debian 13 VM with network connectivity
- Root access to the VM
- Existing DHCP server in your network (will be configured later)
- VM IP address (we'll use
YOUR_VM_IPas placeholder)
SSH into your VM and install all required packages:
apt update
apt install -y \
tftpd-hpa \
nginx \
wget \
curl \
ipxe \
libguestfs-tools \
qemu-utils \
gitCreate the necessary directories on the VM:
mkdir -p /srv/tftp
mkdir -p /srv/boot/ipxe
mkdir -p /srv/boot/debian-13/amd64
mkdir -p /srv/boot/cloud-init/debian-13
mkdir -p /srv/images/debian-13Copy the TFTP configuration:
cp config/tftpd-hpa /etc/default/tftpd-hpaInstall iPXE bootloaders:
cp /usr/lib/ipxe/undionly.kpxe /srv/tftp/
cp /usr/lib/ipxe/ipxe.efi /srv/tftp/
cp /usr/lib/ipxe/snponly.efi /srv/tftp/
chmod 644 /srv/tftp/*
chown -R tftp:tftp /srv/tftp/Restart TFTP service:
systemctl restart tftpd-hpa
systemctl status tftpd-hpaCopy nginx site configuration:
cp config/nginx-pxe-boot /etc/nginx/sites-available/pxe-boot
ln -s /etc/nginx/sites-available/pxe-boot /etc/nginx/sites-enabled/
rm /etc/nginx/sites-enabled/defaultTest nginx configuration:
nginx -tIf test passes, restart nginx:
systemctl restart nginx
systemctl status nginxIMPORTANT: Before copying, update the IP address in ipxe/chain.ipxe.
Edit ipxe/chain.ipxe and replace SERVER_IP_PLACEHOLDER with your VM's IP address.
Then copy iPXE scripts:
cp ipxe/chain.ipxe /srv/boot/ipxe/
cp ipxe/menu.ipxe /srv/boot/ipxe/
cp ipxe/debian-13.ipxe /srv/boot/ipxe/Set permissions:
chown -R www-data:www-data /srv/boot/
chmod -R 644 /srv/boot/ipxe/*IMPORTANT: Before copying, update cloud-init/debian-13/user-data with your SSH public key.
Edit cloud-init/debian-13/user-data and replace YOUR_SSH_PUBLIC_KEY_HERE with your actual SSH public key.
Then copy cloud-init files:
cp cloud-init/debian-13/meta-data /srv/boot/cloud-init/debian-13/
cp cloud-init/debian-13/user-data /srv/boot/cloud-init/debian-13/Set permissions:
chown -R www-data:www-data /srv/boot/cloud-init/
chmod -R 644 /srv/boot/cloud-init/debian-13/*Copy the download script:
cp scripts/download-boot-images.sh /usr/local/bin/
chmod +x /usr/local/bin/download-boot-images.shCopy systemd service file:
cp config/download-boot-images.service /etc/systemd/system/Reload systemd and enable service:
systemctl daemon-reload
systemctl enable download-boot-images.serviceRun the download script manually for the first time:
/usr/local/bin/download-boot-images.shThis will:
- Download Debian 13 cloud image (~200-400MB, may take several minutes)
- Extract kernel and initrd from the image
- Place them in
/srv/boot/debian-13/amd64/
Monitor progress:
tail -f /var/log/download-boot-images.logVerify files were created:
ls -lh /srv/boot/debian-13/amd64/
# Should show: vmlinuz, initrd.img, version.txtTest that nginx is serving files correctly:
# Test iPXE scripts
curl http://localhost/boot/ipxe/menu.ipxe
# Test boot files
curl http://localhost/boot/debian-13/amd64/version.txt
# Test cloud-init
curl http://localhost/boot/cloud-init/debian-13/user-dataAll commands should return file contents without errors.
From another machine on the network (or from the VM itself):
tftp YOUR_VM_IP
> get undionly.kpxe
> quit
ls -lh undionly.kpxe
# Should show file ~100KBNow you need to configure your existing DHCP server to point clients to this PXE boot server.
Add to your DHCP configuration (usually /etc/dhcp/dhcpd.conf):
subnet 192.168.1.0 netmask 255.255.255.0 {
# ... your existing config ...
# PXE Boot Configuration
next-server YOUR_VM_IP;
# Architecture-specific boot files
if option architecture-type = 00:00 {
# BIOS x86
filename "undionly.kpxe";
} elsif option architecture-type = 00:07 {
# UEFI x86_64
filename "ipxe.efi";
} elsif option architecture-type = 00:09 {
# UEFI x86_64 (alternative)
filename "ipxe.efi";
}
}Restart DHCP:
systemctl restart isc-dhcp-serverAdd to your dnsmasq configuration (usually /etc/dnsmasq.conf):
# Enable TFTP
enable-tftp
tftp-root=/srv/tftp
# PXE Boot Configuration
dhcp-match=set:bios,option:client-arch,0
dhcp-match=set:efi64,option:client-arch,7
dhcp-match=set:efi64,option:client-arch,9
dhcp-boot=tag:bios,undionly.kpxe,YOUR_VM_IP
dhcp-boot=tag:efi64,ipxe.efi,YOUR_VM_IPRestart dnsmasq:
systemctl restart dnsmasq- Go to Services → DHCP Server
- Scroll to "Network Booting" section
- Enable network booting
- Set "Next Server" to
YOUR_VM_IP - Set "Default BIOS file name" to
undionly.kpxe - Set "UEFI 64 bit file name" to
ipxe.efi - Save
- Boot a test VM or physical machine
- Configure BIOS/UEFI to boot from network
- Watch the boot sequence:
- DHCP request → receives IP + boot server info
- TFTP → downloads iPXE bootloader
- iPXE menu appears with 10-second timeout
- Default option: Debian 13
- System boots and cloud-init provisions it
Monitor logs in real-time during boot:
# TFTP logs
journalctl -u tftpd-hpa -f
# nginx access logs
tail -f /var/log/nginx/pxe-boot-access.log
# nginx error logs
tail -f /var/log/nginx/pxe-boot-error.logExpected boot sequence in nginx logs:
- Request for
/boot/ipxe/menu.ipxe - Request for
/boot/ipxe/debian-13.ipxe - Request for
/boot/debian-13/amd64/vmlinuz - Request for
/boot/debian-13/amd64/initrd.img - Request for
/boot/cloud-init/debian-13/meta-data - Request for
/boot/cloud-init/debian-13/user-data
Check TFTP service:
systemctl status tftpd-hpa
journalctl -u tftpd-hpa -n 50Verify files exist:
ls -la /srv/tftp/Check firewall:
ufw status
# If enabled, allow TFTP:
ufw allow 69/udpCheck nginx service:
systemctl status nginx
nginx -tVerify file permissions:
ls -la /srv/boot/Check firewall:
ufw status
# If enabled, allow HTTP:
ufw allow 80/tcpCheck that DHCP is providing correct next-server and filename.
From a booting client, check DHCP options received.
Verify iPXE can reach HTTP server:
- Boot to iPXE shell (if you can)
- Type:
dhcp - Type:
show ${next-server} - Type:
chain http://${next-server}/boot/ipxe/menu.ipxe
Check logs:
tail -f /var/log/download-boot-images.logCommon issues:
- No internet connectivity
- libguestfs-tools not installed
- Insufficient disk space
- Cloud image URL changed
Check kernel parameters include cloud-init datasource:
ds=nocloud-net;s=http://YOUR_VM_IP/boot/cloud-init/debian-13/
Verify cloud-init files are accessible:
curl http://YOUR_VM_IP/boot/cloud-init/debian-13/meta-data
curl http://YOUR_VM_IP/boot/cloud-init/debian-13/user-dataIf you have a firewall enabled, allow these ports:
# TFTP (UDP)
ufw allow 69/udp
# HTTP (TCP)
ufw allow 80/tcp- HTTP (unencrypted) - credentials visible in cloud-init
- No authentication on TFTP/HTTP
- Anyone on network can PXE boot
- Use HTTPS for nginx - encrypt cloud-init data
- Network isolation - Put boot server on management VLAN
- Firewall rules - Only allow boot clients to access server
- Remove autoindex - Disable directory browsing in nginx
- MAC address filtering - Only allow specific MACs to boot
The download script checks if images already exist before downloading. To force update:
rm -rf /srv/images/debian-13/debian-13-generic-amd64.qcow2
rm -rf /srv/boot/debian-13/amd64/*
/usr/local/bin/download-boot-images.shsystemctl status tftpd-hpa
systemctl status nginx
systemctl status download-boot-images# TFTP logs
journalctl -u tftpd-hpa
# nginx logs
tail -f /var/log/nginx/pxe-boot-access.log
tail -f /var/log/nginx/pxe-boot-error.log
# Image download logs
tail -f /var/log/download-boot-images.logOnce the basic setup is working:
- Add more OS images - Create similar structures for other distributions
- Customize cloud-init - Add your specific provisioning needs
- Add preseed for installation - Convert PXE boot to automated installer
- Containerize - Move to Docker/Kubernetes (see CONTAINERIZATION.md)
- Enable HTTPS - Encrypt boot traffic
You'll know everything is working when:
- TFTP service is running and accessible
- nginx is serving files on port 80
- Boot images are downloaded and extracted
- PXE client boots and shows iPXE menu
- Debian 13 boots successfully
- Cloud-init provisions the system
- You can SSH into the booted system
If you encounter issues:
- Check logs (see Troubleshooting section)
- Verify all files are in correct locations
- Verify IP addresses are correct in all configs
- Test each component individually (TFTP, HTTP, etc.)
- Check network connectivity between components