Skip to content

PXE: Ubuntu live-server over TFTP

Install servers without touching a USB stick: the firmware fetches grub over TFTP, grub fetches kernel and initrd over TFTP, and the installer fetches the live ISO over HTTP. One tftp server, one web server, and a machine that installs itself.

Server side

apt-get install tftpd-hpa            # the TFTP service
apt install tftp-hpa                 # for local testing
apt-get install apache2              # for the live filesystem

The netboot EFI binaries — the firmware's entry point:

cd /srv/tftp/EFI/BOOT
wget https://releases.ubuntu.com/24.04/netboot/amd64/grubx64.efi
wget https://releases.ubuntu.com/24.04/netboot/amd64/bootx64.efi

Kernel and initrd from the ISO you intend to serve:

wget https://releases.ubuntu.com/24.04.3/ubuntu-24.04.3-live-server-amd64.iso -P /var/www/html/
mkdir /var/www/html/ubuntu24.04 /srv/tftp/ubuntu24.04
mount -o ro ubuntu-24.04.3-live-server-amd64.iso /mnt
cp /mnt/casper/vmlinuz /mnt/casper/initrd /srv/tftp/ubuntu24.04/

The grub menu

/srv/tftp/grub/grub.cfg — a local-drive entry first, the installer second:

set timeout=10

menuentry "Boot from local drive" {
    set root=(hd0,gpt1)
    chainloader /EFI/ubuntu/grubx64.efi
}

menuentry "Install Ubuntu Server 24.04" {
    linux (tftp)/ubuntu24.04/vmlinuz ip=dhcp \
        url=http://<server>/ubuntu-24.04.3-live-server-amd64.iso
    initrd (tftp)/ubuntu24.04/initrd
}

The url= parameter is what makes the live installer network-fetched — no squashfs on the TFTP side, just the two small files.

Permissions and the smoke test

chown -R tftp:tftp /srv/tftp
chmod -R 755 /srv/tftp
find /srv/tftp -type f -exec chmod 644 {} \;
chmod 755 /srv/tftp/EFI/BOOT/*.efi

tftp localhost
tftp> binary
tftp> get EFI/BOOT/bootx64.efi
tftp> quit

If the local test passes and real machines still hang, it is permissions on the TFTP root far more often than anything else.

Testing without hardware

A libvirt VM with UEFI firmware boots PXE exactly like the metal does:

<os>
  <type arch='x86_64' machine='pc-q35-6.2'>hvm</type>
  <loader readonly='yes' type='pflash'>/usr/share/OVMF/OVMF_CODE.fd</loader>
  <nvram>/var/lib/libvirt/qemu/nvram/vm-name_VARS.fd</nvram>
  <bootmenu enable='yes'/>
</os>

This saved you a night? I do this for a living: info@wirt.ee.