Automated Network Boot Installation for Debian 11 and 12 Using PXE

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.

Tags: PXE debian Network Boot UEFI automation

Posted on Wed, 20 May 2026 17:21:31 +0000 by JohnM_2000