Building Ubuntu via PXE

I already have a Raspberry Pi serving up Centos over the network, and I wanted to add Ubuntu.

I’d previously had this working over NFS; I stumbled over a backup of my configuration for 14.04, 12.04, and 11.10 using NFS. However, my Centos setup uses http (plus TFTP for the PXE bit) as I’d found NFS was inexplicably glacial when building KVM virtual machines.

There’s a PXE boot example configuration on the community net installation page, but that uses NFS.

Ubuntu’s documentation is badly in need of updating. It’s based one Debian’s docs, but has references like

Exclusions in %packages sections are no longer supported as of Ubuntu 6.10

And it refers to encrypting passwords with MD5, but not SHA.

But, I’ve got a fully automated setup working. The Kickstart configuration in this blog post will wipe a machine and reinstall it, so it’s easy to iterate and improve the build.


To boot linux over TFTP you need two files from the distribution: the kernel, and initrd.

For more on getting the tftp server working, checkout other posts on my site with the tftp tag.

I’ve started with a server build, and downloaded ubuntu-18.04.4-server-amd64.iso

They two files, within the ISO, are:


(2020-04-19 update: see the hardware enablement section below if, for Bionic, you want the 5.x kernel instead of the GA 4.x kernel.)

Wherever you put them within the TFTP directory structure, bear in mind that in pxelinux.cfg/default, you need to set the full path from the TFTP server’s perspective.

Tip: If you run the tftp server in debug mode, you’ll find that PXE boot supports a lot of alternative files which it’ll pull in preference to ‘default’. I have configuration based on the MAC address of the machines I’m building. This makes it easy to provide very customised options for each machine (such as bespoke kernel options) and saves having dozens of menu options for every machine.

Here’s my working configuration.

LABEL /ubuntu/server/bionic/amd64 hostname
kernel /ubuntu/server/bionic/amd64/linux
append initrd=/ubuntu/server/bionic/amd64/initrd.gz DEBIAN_FRONTEND=newt keep-consoles=true ks=

Boot parameters from the 18.04 docs.

The ‘newt’ frontend is a little nicer than ‘text’. ‘keep-consoles’ ensures you can access shell prompts on other consoles. I was also under the impression that it kept them running at the end of the build as well, but the docs I linked to don’t mention this.


18.04 docs.  There’s a community page as well, but I found it more out of date and incomplete than the official docs.

Two pure ways to automate Debian builds: kickstart and preseed.

I ended up with a hybrid approach for two reasons.

  1. I couldn’t find an example PXE configuration with the kernel parameters to pull a preseed file off an http server.
  2. I did find one for kickstart, I already have Centos kickstart files, and the minimal Debian/Ubuntu support for kickstart can be extended by adding preseed settings.

Start by mounting the ISO within your web server. Mine’s running Centos with SELinux, so I need to provide the context.

/srv/http/iso/ubuntu-18.04.4-server-amd64.iso  /srv/http/pxe/ubuntu/server/bionic/amd64  iso9660  ro,context="system_u:object_r:httpd_sys_content_t:s0" 0 0

My web server root is: /srv/http/pxe – within that there’s directories call centos, ubuntu, and ks, amongst others.

The web server needs to host the kickstart file referred to in the TFTP boot params.

The Ubuntu docs provide an example to get started with. I used that, with parameters and enhancements based on my Centos build.

# kickstart part 1
lang en_GB.UTF-8
langsupport en_GB
keyboard uk
timezone Europe/London
rootpw --disabled

Creating a user is required in the standard Ubuntu way, and the docs state that

–iscrypted option may be used to state that the password is already MD5-hashed

My Centos kickstart files use SHA passwords. I have a perl script for creating them, but in the Ubuntu docs they document a command, from the whois package, to create them:

mkpasswd -m sha-512

The example user kickstart instruction has the password in the plain. This is what it looks like with an encrypted password (in place of ‘xxxx’) and it works fine with sha-512 strings.

# kickstart part 2
user foo --password=xxxx --iscrypted --fullname="Foo Bar"

If you look at ‘man 3 crypt’ you’ll find documentation for how to identify the algorithm. The encrypted string is three values delimited by dollars:


The ID will be ‘1’ for MD5, and ‘5’ or ‘6’ for SHA256/512 respectively.

The next bank of config handles locating the install image, setting up the network, firewall, and disk. The Ubuntu example uses a UUID to refer to the LVM physical volume, which is odd. I suspect that’s been generated automatically somehow. My Centos ones use a placeholder string, which is much simpler.

# kickstart part 3
url --url

network --bootproto=dhcp --device=enp0s25

bootloader --location=mbr
zerombr yes
clearpart --all --initlabel

part /boot --fstype=ext4 --size=1024 --asprimary
part pv.01 --grow --size=1 --asprimary
volgroup rootvg  pv.01
logvol / --vgname=rootvg --size=10240 --name=rootvol --fstype="ext4"

### logvol swap --vgname=rootvg --size=8192 --name=swapvol

(2020-04-19 update: edited to remove firewall command; see below.)

Swap’s interesting. Ubuntu likes to create swap files. As shown, remarked out, it’ll do that. I’ve not yet tested what happens with a swap partition specified.

I’m planning on creating Kubernetes nodes, so I want them built with no swap. I found a blog post with the fix for this (via preseed).

The post indicates you can use percentage or raw size, this is (no longer) the case: if you specify percentage as zero, it errors and throws you to the installer menu.

My final kickstart entries add preseed parameters; the best-of-both-worlds approach. There’s documentation and examples for preseed, and this is probably how I’ll do the majority of any other customisation I need.

# kickstart part 4
preseed partman-swapfile/size string 0
preseed netcfg/hostname string mynewserver
preseed pkgsel/include string openssh-server
preseed mirror/country string gb
preseed mirror/http/hostname string
preseed mirror/http/directory string /ubuntu

After fixing swap, those entries set the hostname and install the SSH server package.

The last three settings: ‘preseed mirror’

  • They ensure that /etc/apt/sources.list is configured the ‘usual’ way, referring to the internet.
  • With these three settings, I get a build that requires no patching.  I suspect they over-ride some, a lot, or even all of the effect of the ‘url’ kickstart configuration entry.
  • Using kickstart ‘url’ alone works fine. The install runs off the ISO, but your apt sources are set to that as well, which is no good for patching and adding packages.

Hardware Enablement

2020-04-19 I later noticed that I wasn’t getting the later HWE kernel as part of the builds. Useful reference docs:

The way to get an HWE build is to copy over different install files from the ISO for the PXE boot.



Yay for docs.

The example kickstart config has the following in it:

#Firewall configuration
firewall --disabled --trust=eth0 --ssh

So, I ran with that:

firewall --enabled --ssh

It doesn’t work

$ sudo ufw status
Status: inactive

And indeed, looking at the docs, it’s also listed as not working.

I’ve not tried hard, as I expected to need to handle the firewall with Puppet anyway. But, I couldn’t find a way to handle this with preseed either. Best hit was a 3 year old ask-ubuntu thread which suggests using a script. 😥

Leave a Reply

Please log in using one of these methods to post your comment: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s