How to Install Fedora over the Network via PXE Boot

PXE boot lets you install Fedora on a machine with no optical drive or USB by booting the Anaconda installer over the network from a TFTP/DHCP server.

You have a rack of bare-metal servers or a lab full of workstations

Flashing USB drives one by one is slow and error-prone. You want to power the machines on, hit a key, and have Fedora install itself automatically over the network. PXE boot makes that possible, but the first time you try it, the client usually hangs on a DHCP timeout or drops to a blank pxelinux> prompt. The pieces are there, but they are not talking to each other.

What is actually happening

PXE boot chains three network services together in a strict sequence. The client asks for an IP address via DHCP. The DHCP server replies with an IP and tells the client where to find a bootloader file. The client fetches that file via TFTP. The bootloader loads the kernel and initramfs, then points the installer to an HTTP or NFS server hosting the Fedora installation tree. If one link breaks, the whole chain stops. Think of it like a relay race. The DHCP server hands the baton to TFTP. TFTP hands it to the bootloader. The bootloader hands it to the HTTP server. You need all four runners on the track.

Prepare the TFTP root and bootloader

Here is how to install the TFTP daemon and the syslinux bootloader files that Fedora expects to find.

sudo dnf install tftp-server syslinux-tftpboot
# tftp-server provides the socket-activated daemon
# syslinux-tftpboot ships pxelinux.0 and required modules
sudo systemctl enable --now tftp.socket
# socket activation means the service only runs when a client connects

Copy the bootloader files into the TFTP root directory. Fedora places the TFTP root at /var/lib/tftpboot by default. Do not move it to /srv/tftp unless you are rewriting the systemd unit.

sudo cp /usr/share/syslinux/pxelinux.0 /var/lib/tftpboot/
# pxelinux.0 is the legacy BIOS bootloader binary
sudo cp /usr/share/syslinux/ldlinux.c32 /var/lib/tftpboot/
# ldlinux.c32 is a required syslinux module for menu rendering
sudo mkdir -p /var/lib/tftpboot/pxelinux.cfg
# this directory holds the menu configuration files

The TFTP daemon is now listening on UDP port 69. It will serve whatever you place inside /var/lib/tftpboot. Keep the directory structure flat and predictable. Future-you will thank you.

Fetch the netboot kernel and initramfs

Here is how to download the stripped-down kernel and initramfs that the PXE bootloader needs to start Anaconda.

sudo mkdir -p /var/lib/tftpboot/fedora41
# isolate this release version from future upgrades
wget https://dl.fedoraproject.org/pub/fedora/linux/releases/41/Server/x86_64/os/images/pxeboot/vmlinuz \
     -O /var/lib/tftpboot/fedora41/vmlinuz
# vmlinuz is the compressed kernel image
wget https://dl.fedoraproject.org/pub/fedora/linux/releases/41/Server/x86_64/os/images/pxeboot/initrd.img \
     -O /var/lib/tftpboot/fedora41/initrd.img
# initrd.img contains drivers and the installer payload

The netboot images are intentionally small. They only contain enough drivers to mount the network and fetch the real installation tree. Do not replace them with the full ISO kernel. The installer will fail to find its payload.

Write the PXE menu configuration

Here is how to create the syslinux menu file that tells the bootloader which kernel to load and where to find the install tree.

DEFAULT fedora41
# sets the default boot entry if the user presses Enter
LABEL fedora41
# creates a named entry in the boot menu
  MENU LABEL Install Fedora 41
  KERNEL fedora41/vmlinuz
  APPEND initrd=fedora41/initrd.img inst.repo=http://192.168.1.10/fedora41 quiet
# points to the initramfs, tells Anaconda where the packages live, hides boot spam

Replace 192.168.1.10 with the actual IP address of your HTTP server. The inst.repo parameter is mandatory. Without it, Anaconda will drop you into a shell instead of starting the graphical installer. Save the file as /var/lib/tftpboot/pxelinux.cfg/default. Syslinux reads this file automatically when a client requests pxelinux.0.

Host the installation tree over HTTP

Here is how to set up Apache and mirror the Fedora ISO contents so the installer can download packages over the network.

sudo dnf install httpd
# provides the web server for serving the install tree
sudo mkdir -p /var/www/html/fedora41
# creates the document root for this release
sudo mount -o loop Fedora-Server-dvd-x86_64-41.iso /mnt
# mounts the ISO as a read-only loop device
sudo rsync -av /mnt/ /var/www/html/fedora41/
# copies files while preserving symlinks and permissions
sudo umount /mnt
sudo systemctl enable --now httpd
# starts the web server and enables it at boot

HTTP is simpler than NFS for most lab environments. You do not need to configure exports, mount points, or root squashing. The installer treats the HTTP tree exactly like a local DVD. Verify the tree is accessible by running curl -I http://192.168.1.10/fedora41/repodata/repomd.xml. You should see a 200 OK response. If you get a 403 Forbidden, check the SELinux context on /var/www/html/fedora41.

Point DHCP to the network boot chain

Here is how to configure your DHCP server to hand out the TFTP server address and bootloader filename to PXE clients.

next-server 192.168.1.10;
# tells the client where to fetch the bootloader via TFTP
filename "pxelinux.0";
# specifies the legacy BIOS bootloader file name
# filename "grubx64.efi";
# uncomment this line and comment out pxelinux.0 for UEFI clients

Add these lines to the global scope or a specific subnet block in /etc/dhcp/dhcpd.conf. The next-server directive is what bridges DHCP and TFTP. Without it, the client gets an IP address but has no idea where to look for the bootloader. Restart the DHCP daemon after editing. Run journalctl -xeu dhcpd to confirm it loaded the new configuration without syntax errors.

Open the firewall and handle SELinux

Here is how to allow the required network services through the firewall and reload the runtime configuration.

sudo firewall-cmd --permanent --add-service=dhcp
# opens UDP 67/68 for DHCP client and server traffic
sudo firewall-cmd --permanent --add-service=tftp
# opens UDP 69 for TFTP file transfers
sudo firewall-cmd --permanent --add-service=http
# opens TCP 80 for the installation tree
sudo firewall-cmd --reload
# applies the permanent rules to the running firewall

Fedora ships with firewalld enabled and restrictive by default. The --reload command is mandatory. Without it, the runtime configuration and the persistent configuration diverge, and your clients will time out. SELinux usually allows TFTP and HTTP out of the box. If you see denials, check journalctl -t setroubleshoot for the one-line summary. Fix the context with restorecon -Rv instead of disabling enforcement. Trust the package manager. Manual file edits drift, snapshots stay.

Verify the chain works

Here is how to test each link in the PXE chain before powering on a physical machine.

curl -s http://192.168.1.10/fedora41/repodata/repomd.xml | head -n 3
# confirms the HTTP tree is reachable and readable
tftp 192.168.1.10 -c get pxelinux.0 -o /tmp/test-pxe.bin
# verifies TFTP can actually serve the bootloader binary
ls -l /tmp/test-pxe.bin
# checks that the downloaded file is not empty

Spin up a lightweight VM with a network adapter set to "PXE boot first". Attach it to the same bridge or virtual network as your server. Watch the boot sequence. The client should request an IP, fetch pxelinux.0, display the menu, and launch Anaconda. If it hangs at "DHCP timeout", check your router and DHCP lease scope. If it drops to pxelinux>, the menu file is missing or misnamed. Reboot before you debug. Half the time the symptom is gone.

Common pitfalls and exact error strings

The pxelinux.0 bootloader will refuse to load and print Could not find kernel image: fedora41/vmlinuz. The path is relative to the TFTP root. Check your directory structure and spelling. TFTP is case-sensitive.

If you see [FAILED] Failed to start tftp.socket in the journal, another service is already binding to UDP port 69. Run ss -ulnp | grep :69 to find the conflict. Stop the rogue service before enabling tftp.socket.

The installer will abort with Error: Cannot retrieve repository metadata (repomd.xml) for repository: base. The HTTP server is either down, the path is wrong, or SELinux is blocking the request. Check journalctl -xeu httpd and verify the inst.repo URL matches the actual directory layout.

When to use PXE versus other methods

Use PXE boot when you are deploying dozens of identical machines in a controlled network. Use USB installation when you are setting up a single workstation or working outside a managed network. Use Anaconda live ISO when you need to troubleshoot hardware compatibility before committing to an install. Use Kickstart over HTTP when you want to automate partitioning and package selection without touching the keyboard. Stay on the upstream Workstation ISO if you only deviate from the defaults occasionally.

Where to go next