Core Components of a PXE Infrastructure
Network booting utilizes the Preboot eXecution Environment (PXE) protocol to load an operating system over the network. A functional PXE infrastructure relies on three primary services:
- DHCP Server: Allocates IP addresses and directs clients to the network boot loader.
- TFTP Server: Transfers the initial boot loader, kernel, and initrd files to the client.
- HTTP Server: Delivers the OS repository and unattended installation configurations.
On Debian, isc-dhcp-server, tftpd-hpa, and apache2 satisfy these requirements effectively.
Key Configuration Paths
- DHCP configuration:
/etc/dhcp/dhcpd.conf - TFTP settings:
/etc/default/tftpd-hpa - TFTP root directory:
/var/lib/tftpboot - Apache root directory:
/var/www/html - BIOS boot menu:
/var/lib/tftpboot/pxelinux.cfg/default - UEFI boot menu:
/var/lib/tftpboot/grub/grub.cfg - Unattended script:
/var/www/html/preseed/debian_auto.cfg
Automated Deployment Script
The following script configures the PXE server to support both Legacy BIOS and UEFI architectures, utilizing LVM partitioning with the XFS filesystem. Before execution, ensure the default shell is bash by running ln -sf bash /bin/sh. Adjust the variables in lines 15-40 to match your network environment. Root and user passwords must be generated using SHA-512 hashing (install whois and use mkpasswd -m sha-512).
#!/bin/bash
# Debian 12 PXE Server Setup for Debian 11 & 12 (BIOS + UEFI)
# Detect primary network interface
ACTIVE_NIC=$(ip route | grep default | awk '{print $5}' | head -n 1)
SRV_IP=$(hostname -I | awk '{print $1}')
# Network Variables
SUBNET="10.0.0.0"
NETMASK="255.255.255.0"
BROADCAST="10.0.0.255"
GATEWAY="10.0.0.1"
DNS_SERVER="1.1.1.1"
DHCP_RANGE="10.0.0.100 10.0.0.200"
# TFTP Settings
TFTP_PORT=69
TFTP_DAEMON_USER="tftp"
TFTP_ROOT="/var/lib/tftpboot"
# OS Directories
DEBIAN11_DIR="bullseye"
DEBIAN12_DIR="bookworm"
# Remote Netboot Archives
NETBOOT11_URL="https://mirrors.ustc.edu.cn/debian/dists/Debian11.10/main/installer-amd64/current/images/netboot/netboot.tar.gz"
NETBOOT12_URL="https://mirrors.ustc.edu.cn/debian/dists/Debian12.6/main/installer-amd64/current/images/netboot/netboot.tar.gz"
# Credentials and Locale
ROOT_HASH='$6$SALT_HASH$your_sha512_hash_here'
HOSTNAME_STR="debian-node"
TIMEZONE="UTC"
APT_MIRROR="deb.debian.org"
# Apache Settings
WEB_ROOT="/var/www/html"
setup_pxe_environment() {
if dpkg -l | grep -q isc-dhcp-server; then
echo "PXE Server components are already installed."
exit 0
fi
echo "Installing core packages..."
apt update
apt install -y firewalld whois isc-dhcp-server tftpd-hpa apache2 syslinux pxelinux
# Firewall rules
firewall-cmd --permanent --add-port=${TFTP_PORT}/tcp
firewall-cmd --permanent --add-port=${TFTP_PORT}/udp
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --reload
# DHCP Configuration
sed -i "s/^INTERFACESv4=.*$/INTERFACESv4=\"$ACTIVE_NIC\"/" /etc/default/isc-dhcp-server
cat > /etc/dhcp/dhcpd.conf <<EOFDHCP
option domain-name "$DNS_SERVER";
option domain-name-servers $DNS_SERVER;
default-lease-time 2592000;
max-lease-time 2592000;
authoritative;
option space pxelinux;
option pxelinux.magic code 208 = string;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;
option architecture-type code 93 = unsigned integer 16;
subnet $SUBNET netmask $NETMASK {
range dynamic-bootp $DHCP_RANGE;
option broadcast-address $BROADCAST;
option routers $GATEWAY;
class "pxeclients" {
match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
next-server $SRV_IP;
if option architecture-type = 00:07 {
filename "bootx64.efi";
} else {
filename "pxelinux.0";
}
}
}
EOFDHCP
# TFTP Configuration
cat > /etc/default/tftpd-hpa <<EOFTFTP
TFTP_USERNAME="$TFTP_DAEMON_USER"
TFTP_DIRECTORY="$TFTP_ROOT"
TFTP_ADDRESS=":$TFTP_PORT"
TFTP_OPTIONS="--secure"
EOFTFTP
mkdir -p $TFTP_ROOT/{$DEBIAN11_DIR,$DEBIAN12_DIR,pxelinux.cfg,grub}
# Fetch and extract netboot assets
curl -o /tmp/netboot11.tar.gz $NETBOOT11_URL
curl -o /tmp/netboot12.tar.gz $NETBOOT12_URL
tar -xzf /tmp/netboot11.tar.gz -C $TFTP_ROOT/$DEBIAN11_DIR
tar -xzf /tmp/netboot12.tar.gz -C $TFTP_ROOT/$DEBIAN12_DIR
# BIOS Boot Menu
cat > $TFTP_ROOT/pxelinux.cfg/default <<EOFBIOS
default vesamenu.c32
menu hshift 13
menu width 49
menu margin 8
menu tabmsg
timeout 100
menu title Network Boot Menu
label Auto_Install_Debian11
menu label ^Automated Install Debian 11 (BIOS)
menu default
kernel $DEBIAN11_DIR/debian-installer/amd64/linux
append auto=true priority=critical vga=788 initrd=$DEBIAN11_DIR/debian-installer/amd64/initrd.gz url=http://${SRV_IP}/preseed/debian_auto.cfg
label Auto_Install_Debian12
menu label ^Automated Install Debian 12 (BIOS)
kernel $DEBIAN12_DIR/debian-installer/amd64/linux
append auto=true priority=critical vga=788 initrd=$DEBIAN12_DIR/debian-installer/amd64/initrd.gz url=http://${SRV_IP}/preseed/debian_auto.cfg
label local_boot
com32 chain.c32
menu label Boot from ^local disk
localboot 0xffff
EOFBIOS
# Copy syslinux binaries
cp /usr/lib/syslinux/modules/bios/* $TFTP_ROOT/
cp /usr/lib/PXELINUX/pxelinux.0 $TFTP_ROOT/
# Prepare UEFI Boot Loaders
mkdir -p /tmp/efi_extract
cd /tmp/efi_extract
apt download shim-signed
dpkg -x shim-signed*.deb .
cp ./usr/lib/shim/shimx64.efi.signed $TFTP_ROOT/bootx64.efi
apt download grub-efi-amd64-signed
dpkg -x grub-efi-amd64-signed*.deb .
cp ./usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed $TFTP_ROOT/grubx64.efi
apt download grub-common
dpkg -x grub-common*.deb .
cp ./usr/share/grub/unicode.pf2 $TFTP_ROOT/
cd /
rm -rf /tmp/efi_extract
# UEFI Boot Menu
cat > $TFTP_ROOT/grub/grub.cfg <<EOFUEFI
set default=0
set timeout=10
set gfxpayload=keep
insmod gzio
insmod part_gpt
insmod ext2
insmod xfs
insmod png
insmod gfxterm
terminal_output gfxterm
menuentry 'Automated Install Debian 11 (UEFI)' {
linuxefi $DEBIAN11_DIR/debian-installer/amd64/linux ip=dhcp auto=true priority=critical vga=788 url=http://${SRV_IP}/preseed/debian_auto.cfg
initrdefi $DEBIAN11_DIR/debian-installer/amd64/initrd.gz
}
menuentry 'Automated Install Debian 12 (UEFI)' {
linuxefi $DEBIAN12_DIR/debian-installer/amd64/linux ip=dhcp auto=true priority=critical vga=788 url=http://${SRV_IP}/preseed/debian_auto.cfg
initrdefi $DEBIAN12_DIR/debian-installer/amd64/initrd.gz
}
menuentry 'Local Boot' {
exit
}
EOFUEFI
# Preseed Automation Configuration
mkdir -p $WEB_ROOT/preseed
cat > $WEB_ROOT/preseed/debian_auto.cfg <<EOFPRESEED
d-i debian-installer/locale string en_US
d-i debian-installer/language string en
d-i debian-installer/country string US
d-i debian-installer/locale string en_US.UTF-8
d-i localechooser/supported-locales multiselect en_US.UTF-8, zh_CN.UTF-8
d-i keyboard-configuration/xkb-keymap select us
d-i netcfg/choose_interface select auto
d-i netcfg/get_hostname string $HOSTNAME_STR
d-i netcfg/get_domain string local
d-i mirror/country string manual
d-i mirror/protocol string http
d-i mirror/http/hostname string $APT_MIRROR
d-i mirror/http/directory string /debian
d-i mirror/http/proxy string
tasksel tasksel/first multiselect standard ssh-server
d-i passwd/root-login boolean true
d-i passwd/make-user boolean false
d-i passwd/root-password-crypted password $ROOT_HASH
d-i user-setup/allow-password-weak boolean true
d-i clock-setup/utc boolean true
d-i time/zone string $TIMEZONE
d-i clock-setup/ntp boolean false
d-i partman-efi/non_efi_system boolean true
d-i partman-partitioning/choose_label string gpt
d-i partman-partitioning/default_label string gpt
d-i partman-auto/method string lvm
d-i partman-auto/disk string /dev/sda
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
d-i partman-auto/choose_recipe select atomic
d-i partman/default_filesystem string xfs
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
d-i apt-setup/non-free boolean true
d-i apt-setup/contrib boolean true
d-i apt-setup/cdrom/set-first boolean false
d-i apt-setup/cdrom/set-next boolean false
d-i apt-setup/cdrom/set-failed boolean false
d-i pkgsel/include string openssh-server vim sudo git curl
d-i pkgsel/upgrade select none
d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true
d-i grub-installer/bootdev string default
d-i finish-install/reboot_in_progress note
d-i preseed/late_command string in-target sed -i '$ a\\PermitRootLogin yes' /etc/ssh/sshd_config
EOFPRESEED
# Enable and start services
systemctl enable tftpd-hpa isc-dhcp-server apache2
systemctl restart tftpd-hpa isc-dhcp-server apache2
echo "PXE Server configuration completed successfully."
}
setup_pxe_environment
Executing the Setup
Save the script to a file, adjust the network variables, and execute it with root privileges:
chmod +x pxe_setup.sh
./pxe_setup.sh
Client machines can now be configured to boot via network. Legacy BIOS clients will receive the SYSLINUX menu, while UEFI clients will be presented with the GRUB menu. Both architectures will automatically pull the preseed configuration and install Debian without manual intervention.