< BACK

System administration mini tutorials and how-to guides

Tutorial: installing a PXE server for networking booting on Ubuntu 18.04.4 LTS

Updated: 2020-08-06

Overview

This describes a basic approach for the setup of running a PXE server on an Ubuntu machine using dnsmasq and serving images via NFS within a network behind a router, in my case Fritzbox.

Sources and references

This guide was adapted from the following sources:

The following references may also be helpful:

Setup

IP addresses

In my example, the IP addresses are allocated as follows:

Device IP address/range
Network address 192.168.178.0
Gateway (router) 192.168.178.1
Server running PXE 192.168.178.100

Required packages on Ubuntu

sudo apt install -y nfs-kernel-server
sudo apt install -y syslinux pxelinux dnsmasq

Installation process

This installation will use the following path for the tftp server:

/srv/tftp

Configuration for dnsmasq

Set the variables as appropriate before running the following bash commands to create the configuration:

NETWORK_ADDR="192.168.178.0"
GW_ADDR="192.168.178.1"
OWN_ADDR="192.168.178.100"
TFTP_ROOT=/srv/tftp

FILE="/etc/dnsmasq.conf"
[ -f $FILE ] && sudo cp --force --backup=numbered "$FILE" "$FILE" # backup any existing conf

read -r -d '' VAR <<EOF
log-dhcp

resolv-file=/etc/resolv.conf.dnsmasq
domain-needed
bogus-priv

dhcp-range=${NETWORK_ADDR},proxy

dhcp-boot=pxelinux.0,${OWN_ADDR},${NETWORK_ADDR}

pxe-prompt="Press F8 for menu.", 8
pxe-service=X86PC,     "Legacy Network boot", /srv/tftp/pxelinux
pxe-service=X86-64_EFI,"EFI Network boot",    /srv/tftp/pxelinux

# local tftp-server
enable-tftp
tftp-root=${TFTP_ROOT}
EOF

echo "$VAR" | sudo tee "$FILE"

The file should now look like this:

log-dhcp

resolv-file=/etc/resolv.conf.dnsmasq
domain-needed
bogus-priv

dhcp-range=192.168.178.0,proxy

dhcp-boot=pxelinux.0,192.168.178.100,192.168.178.0

pxe-prompt="Press F8 for menu.", 8
pxe-service=X86PC,     "Legacy Network boot", /srv/tftp/pxelinux
pxe-service=X86-64_EFI,"EFI Network boot",    /srv/tftp/pxelinux

# local tftp-server
enable-tftp
tftp-root=/srv/tftp

Now create the resolv file

GW_ADDR="192.168.178.1"
FILE="/etc/resolv.conf.dnsmasq"
[ -f $FILE ] && sudo cp --force --backup=numbered "$FILE" "$FILE"

read -r -d '' VAR <<EOF
nameserver ${GW_ADDR} # gateway or router IP
# if necessary further nameservers (external)
nameserver 8.8.8.8
nameserver 8.8.8.4
EOF

echo "$VAR" | sudo tee "$FILE"

Then restart the dnsmasq service:

sudo service dnsmasq restart

At this point booting from somewhere on the network (in this case using VirtualBox) could look something like this:

360b1123d70fd6151b79460c194b3603.png

and in the /var/log/syslog file the following lines should show up:

Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 available DHCP subnet: 192.168.178.0/255.255.255.0
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 vendor class: PXEClient:Arch:00000:UNDI:002001
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 user class: iPXE
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 PXE(enp2s0) 08:00:27:38:96:d0 proxy
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 tags: enp2s0
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 bootfile name: pxelinux.0
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 next server: 192.168.178.0
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 broadcast response
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 sent size:  1 option: 53 message-type  2
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 sent size:  4 option: 54 server-identifier  192.168.178.100
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 sent size:  9 option: 60 vendor-class  50:58:45:43:6c:69:65:6e:74
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 sent size: 17 option: 97 client-machine-id  00:74:c4:a9:4a:a5:d3:46:45:81:f8:1d:fe:f3...
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 sent size: 58 option: 43 vendor-encap  06:01:03:08:07:80:00:01:c0:a8:b2:64:09:16...
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 available DHCP subnet: 192.168.178.0/255.255.255.0
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 vendor class: PXEClient:Arch:00000:UNDI:002001
Aug  2 11:32:50 elqt dnsmasq-dhcp[15024]: 3170770957 user class: iPXE
Aug  2 11:32:58 elqt dnsmasq-dhcp[15024]: 0 available DHCP subnet: 192.168.178.0/255.255.255.0
Aug  2 11:32:58 elqt dnsmasq-dhcp[15024]: 0 vendor class: PXEClient:Arch:00000:UNDI:002001
Aug  2 11:32:58 elqt dnsmasq-dhcp[15024]: 0 user class: iPXE
Aug  2 11:32:58 elqt dnsmasq-dhcp[15024]: 0 PXE(enp2s0) 192.168.178.38 08:00:27:38:96:d0 /srv/tftp/pxelinux.0
Aug  2 11:32:58 elqt dnsmasq-dhcp[15024]: 0 tags: enp2s0
Aug  2 11:32:58 elqt dnsmasq-dhcp[15024]: 0 bootfile name: /srv/tftp/pxelinux.0
Aug  2 11:32:58 elqt dnsmasq-dhcp[15024]: 0 next server: 192.168.178.100
Aug  2 11:32:58 elqt dnsmasq-dhcp[15024]: 0 sent size:  1 option: 53 message-type  5
Aug  2 11:32:58 elqt dnsmasq-dhcp[15024]: 0 sent size:  4 option: 54 server-identifier  192.168.178.100
Aug  2 11:32:58 elqt dnsmasq-dhcp[15024]: 0 sent size:  9 option: 60 vendor-class  50:58:45:43:6c:69:65:6e:74
Aug  2 11:32:58 elqt dnsmasq-dhcp[15024]: 0 sent size: 17 option: 97 client-machine-id  00:74:c4:a9:4a:a5:d3:46:45:81:f8:1d:fe:f3...
Aug  2 11:32:58 elqt dnsmasq-dhcp[15024]: 0 sent size: 28 option: 43 vendor-encap  47:04:80:00:00:00:0a:13:08:50:72:65:73:73...
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: sent /srv/tftp/pxelinux.0 to 192.168.178.38
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: sent /srv/tftp/ldlinux.c32 to 192.168.178.38
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: file /srv/tftp/pxelinux.cfg/74c4a94a-a5d3-4645-81f8-1dfef39eea6f not found
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: file /srv/tftp/pxelinux.cfg/01-08-00-27-38-96-d0 not found
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: file /srv/tftp/pxelinux.cfg/C0A8B226 not found
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: file /srv/tftp/pxelinux.cfg/C0A8B22 not found
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: file /srv/tftp/pxelinux.cfg/C0A8B2 not found
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: file /srv/tftp/pxelinux.cfg/C0A8B not found
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: file /srv/tftp/pxelinux.cfg/C0A8 not found
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: file /srv/tftp/pxelinux.cfg/C0A not found
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: file /srv/tftp/pxelinux.cfg/C0 not found
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: file /srv/tftp/pxelinux.cfg/C not found
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: sent /srv/tftp/pxelinux.cfg/default to 192.168.178.38
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: sent /srv/tftp/vesamenu.c32 to 192.168.178.38
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: sent /srv/tftp/libcom32.c32 to 192.168.178.38
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: sent /srv/tftp/libutil.c32 to 192.168.178.38
Aug  2 11:32:58 elqt dnsmasq-tftp[15024]: sent /srv/tftp/pxelinux.cfg/default to 192.168.178.38

Configuring the live netboot files and live image

Creating file structure from ISO image

Obtain the ISO image, e.g.

wget https://releases.ubuntu.com/18.04/ubuntu-18.04.4-desktop-amd64.iso

Then create a mount point and mount it, create necessary directories and copy files as follows, noting that for recent Ubuntu releases (e.g. 20.04) there is a hidden .disk subdirectory which needs to be copied so use a . (dot) with the cp command:

sudo mkdir /mnt/ubu-1804/
sudo mount -o loop ubuntu-18.04.4-desktop-amd64.iso /mnt/ubu-1804/
sudo mkdir -p /srv/{nfs,tftp}/ubuntu1804
sudo cp -Rfv /mnt/ubu-1804/. /srv/nfs/ubuntu1804/
sudo cp -v /srv/nfs/ubuntu1804/casper/{vmlinuz,initrd*} /srv/tftp/ubuntu1804/

after which the ISO image can be unmounted and deleted or archived.

sudo umount /mnt/ubu-1804

This step can be automated using variables to facilitate further images being added later:

MNT_PT="/mnt/ubu-1804"
ISO="ubuntu-18.04.4-desktop-amd64.iso"
SUBDIR="ubuntu1804"
sudo mkdir ${MNT_PT}
sudo mount -o loop "${ISO}" "${MNT_PT}"
sudo mkdir -pv /srv/{nfs,tftp}/${SUBDIR}
sudo cp -iRfv ${MNT_PT}/. /srv/nfs/${SUBDIR} # copy contents of ISO to nfs dir
sudo cp -iv ${MNT_PT}/casper/{vmlinuz,initrd*} /srv/tftp/${SUBDIR}
sudo umount "${MNT_PT}" # unmount iso

Creating the PXE boot entry

Create the tftp root directory and pxelinux.cfg directory in a single step:

sudo mkdir -p /srv/tftp/pxelinux.cfg

Ubuntu 18.04 LTS Desktop uses casper for booting into live mode and casper only supports network boot via NFS. As such booting via PXE requires a network accessible NFS server. This will have been installed and setup via apt above and a nfs share directory /srv/nfs also created above.

Add the following line to /etc/exports

/srv/nfs *(ro,sync,no_wdelay,insecure_locks,no_root_squash,insecure,no_subtree_check)

thus and finally make this nfs share available:

if ! egrep -q '^/srv/nfs' /etc/exports; then \
	echo "/srv/nfs  *(ro,sync,no_wdelay,insecure_locks,no_root_squash,insecure,no_subtree_check)"\
	| sudo tee -a /etc/exports; \
fi
sudo exportfs -a

Now copy the PXE files as follows

sudo cp -v /usr/lib/PXELINUX/pxelinux.0 /srv/tftp/ -i
sudo cp -v /usr/lib/syslinux/modules/bios/{ldlinux.c32,libcom32.c32,libutil.c32,vesamenu.c32} /srv/tftp -i

Create the PXE bootloader’s default configuration file:

OWN_ADDR="192.168.178.100"
LABEL="18040001" # label entry identifier (should be unique)
TITLE="Install Ubuntu 18.04 LTS Desktop"
DIRPATH="ubuntu1804" # relative to /srv/tftp and /srv/nfs

FILE="/srv/tftp/pxelinux.cfg/default"
[ -f $FILE ] && sudo cp --force --backup=numbered "$FILE" "$FILE"

# determine whether ``initrd`` or e.g. ``initrd.lz`` is required
for INITRD in /srv/tftp/${SUBDIR}/initrd*; do INITRD="${INITRD##*/}"; done

read -r -d '' VAR <<EOF
default vesamenu.c32

label ${LABEL}
\tmenu label ^${TITLE}
\tmenu default
\tkernel ${DIRPATH}/vmlinuz
\tappend initrd=${DIRPATH}/${INITRD} boot=casper netboot=nfs nfsroot=${OWN_ADDR}:/srv/nfs/${DIRPATH}/ locale=en_US keyboard-configuration/layoutcode=de console-setup/layoutcode=de nosplash toram ---

EOF

echo "$VAR" | sudo tee "$FILE"

which results in the following configuration:

default vesamenu.c32

label 18040001
	menu label ^Install Ubuntu 18.04 LTS Desktop
	menu default
	kernel ubuntu1804/vmlinuz
	append initrd=ubuntu1804/initrd boot=casper netboot=nfs nfsroot=192.168.178.100:/srv/nfs/ubuntu1804/ locale=en_US keyboard-configuration/layoutcode=de console-setup/layoutcode=de nosplash toram ---

Note that in my configuration, above, I deliberately chose to install in English (locale=en_US) but my keyboard layout and configuration is German. These can be adapted or left out.

For Ubuntu 20.04 releases the append line is somewhat different and requires:

ip=dhcp

The following works for me:

append initrd=ubu-20.04-mint-20-cinnamon/initrd.lz nfsroot=192.168.178.100:/srv/nfs/ubu-20.04-mint-20-cinnamon ro netboot=nfs file=/cdrom/preseed/linuxmint.seed boot=casper ip=dhcp -- debian-installer/language=en console-setup/layoutcode=de keyboard-configuration/layoutcode=de keyboard-configuration/variant=German

or using variables,

append initrd=${DIRPATH}/${INITRD} nfsroot=${OWN_ADDR}:/srv/nfs/${DIRPATH} ro netboot=nfs file=/cdrom/preseed/linuxmint.seed boot=casper ip=dhcp nosplash -- debian-installer/language=en console-setup/layoutcode=de keyboard-configuration/layoutcode=de keyboard-configuration/variant=German

It might be necessary at this point to restart dnsmasq and re-read NFS exports:

sudo service dnsmasq restart
sudo exportfs -a

Booting from somewhere on the network should look something like this:

360b1123d70fd6151b79460c194b3603.png

4e933377a3334d1ceece8e91402d2ed0.png

5dfc2d50febcad81edfea844b18abe11.png

Note: When creating additional network bootable images be sure to make the label entry identifier unique!

I hope this tutorial and guide to setting up a PXE boot server on Ubuntu was helpful.