Skip to content

Network Install (PXE / debian-installer)

Today, we’re going to walk through the process of building an unattended install server using network services.  We configure these services on dedicated machine (a server), and we boot the endpoint (target) machines using network boot capable hardware — no removable media mastering need be involved.  In cases where we’ve been able to deploy a dedicated server, this has allowed us the ability to very easily deploy and re-deploy installations and configurations with very little demand on individual resources.  That said, network based deployment solutions do require having very specific network capable hardware as well as very specific access to the network address space.  More often than not, the network environments we’ve worked in have not afforded us these luxuries. All the same, all the the fundamental process of provisioning is the same for both pure network installation and removable media installation, so this will be a good place to start.

A very important caveat: the following deployment model requires not only having a physical network in place for all the machines we wish to deploy, but we require a dedicated network broadcast domain.  This approach has served us well at sites where we have a discrete group of computers on their own dedicated subnet (e.g. a computer lab located in a single room). Topographically, we need to be able to situate our installation server “upstream” of our target machines in such a way that those machines will we have exclusive access to the network services being offered by our installation server.  Likewise, we wish that our installation server only be able to offer services to the target install machines and not to machines elsewhere on the site network (or to the Internet at large!).

Our server will essentially operate in several different roles: 1) a boot server 2) an IP nat router 3) a fileserver for software install files and configuration instructions. For the purposes of this discussion, I’m going to assume a hardware server with 2 distinct network interfaces — one interface connected to the upstream network, and the other connected to a dedicated switch linking all target machines on the same private subnet.

Assuming you have the appropriate network in place, and the hardware configured, install your favorite Ubuntu spin onto your server machine.  I used Ubuntu 18.04 server edition. It doesn’t matter which version of you choose, so long as your distribution can retrieve the tools we’re going to need for the rest of this build. We’re going to want to configure our “inside” network interface with a dedicated, private IP address — notably the host address for our subnet of target machines.  In my case, I chose the subnet, as this did not conflict with the upstream address space.  Your network may vary, and you should consult with your network or LAN administrator before proceeding.


We will install services for DNS, DHCP, TFTP, HTTP, and Apt Caching

# apt-get install dnsmasq apache2 apt-cacher

Next we need to configure the firewall on our server to act a NAT and packet forwarder, as well as to open ports for the services our install server will provide to the target machines.


Edit the following files to use our server as a NAT for our installer subnet:




Uncomment the line “net/ipv4/ip_forward=1”


Add the following lines just after the header comments:

# Forward traffic through wlp3so (my “outside” interface. Change to match your out-interface)

Restart ufw using ufw or systems:

$ sudo ufw disable && sudo ufw enable

We’re also going to need to open up some ports:

# allow clients to receive DHCP, we will further restrict this in the dnsmasq config
ufw allow 67,68/udp
# allow clients to receive TFTP
$ sudo ufw allow proto tcp from to any port 69
# allow clients to access our web server
$ sudo ufw allow proto tcp from to any port 80
# allow clients to access our package cacher
$ sudo ufw allow proto tcp from to any port 3142
# allow clients to use our DNS
$ sudo ufw allow from to any port 53


The package dnsmasq offers us a convenient tool for managing DHCP, DNS, and a TFTP server.


Then we deploy the pre-execution environment (PXE) tools to allow network bootstrapping for out client machines.

$ sudo mkdir /tftpboot
$ sudo cd /tftpboot
$ sudo wget
$ sudo tar -xvf netboot.tar.gz


Add a menu item to /tftpboot/ubuntu-installeri386/boot-screens/txt.cfg Ours looks like this:

label partimus
menu label ^Partimus Install
kernel ubuntu-installer/i386/linux
append vga=788 initrd=ubuntu-installer/i386/initrd.gz debconf-prioroty=high debian-installer/language=en debian-installer/country=US console-setup/ask_detect=false keyboard-configuration/layout-code=us hw-detecy/load-firmware=false netcfg/choose_interface=enp0s25 netcfg/get_hostname?= netcfg-dhcp_timeout=60 preseed/url= --- quiet

What are all these parameters appended to the kernel arguments?  We’ll get into that later when we discuss debian-installer preseed options.  We need to include the parameters in the kernel arguments because the preseed file can’t be read unless the installer knows where to find it.

Official Ubuntu Documentation


On Ubuntu 18.04, we need to ensure that our dedicated DHCP server and the local DNS resolver can co-exist.

Edit /etc/systemd/resolved.conf

And uncomment the directive DNSStubListener= to read DNSStubListener=no.

Restart systemd-resolved:

$ sudo systemctl restart systemd-resolved

Enable dnsmasq services:

$ sudo systemctl enable dnsmasq


Uncomment the line “allowed_hosts = *” and change it to read “allowed_hosts =” (or the subnet in your environment)

Also ensure that Apt-cacher is automatically started when we start our web server:

Edit the file /etc/default/apt-cacher and ensure that the directive “AUTOSTART=“ is set to “AUTOSTART=1”

Enable apache.

$ sudo systemctl enable apache2


Next we need to generate and host the “preseed” configuration files. This file provides the “answers” to the questions that debian-installer and our OS and software packages will want to know.

The preseed file is the “recipe” for configuring our install.  It consists of data directives which are interpreted by the installer and other packages and stored in a database called “debconf”.

Official Ubuntu Documentation

On any installed system, using the tool “debconf-get-selections” from the debconf-utils package will output all the debian configuration settings for the system. Often, it becomes an iterative processs of installing a system manually, noting the appropriate debconf directives, and then incorporating them into your preseed configuration recipe.

Here’s the preseed file for a Partimus workstation. Apache serves this file from it’s default directory at /var/www/html.

#### preseed.cfg
## lubuntu 18.04
## for standard workstation

### Locale

d-i debian-installer/locale string en_US.UTF-8
d-i debian-installer/locale string en_US
d-i console-setup/ask_detect boolean false
d-i console-setup/layoutcode string us
d-i keyboard-configuration/xkb-keymap select us

### Network configuration

d-i netcfg/get_hostname string unassigned-hostname
d-i netcfg/get_domain string unassigned-domain
d-i netcfg/choose_interface select auto
d-i netcfg/wireless_wep string

### Mirror settings

d-i mirror/country string manual
d-i mirror/http/hostname string
d-i mirror/http/directory string /
d-i mirror/http/proxy string
apt-setup/services-select multiselect security
d-i apt-setup/security_host string
d-i apt-setup/security_path string /
d-i mirror/suite string bionic
d-i mirror/udeb/suite string bionic
d-i debian-installer/allow_unauthenticated string true

### Partitioning

d-i partman-auto/method string regular
d-i partman-auto/init_automatically_partition select Guided - use entire disk
d-i partman-auto/purge_lvm_from_device boolean true
d-i partman-lvm/confirm boolean true
d-i partman/choose_partition select Finish partitioning and write changes to disk
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite true

#d-i partman-auto/method string lvm
d-i partman-lvm/device_remove_lvm boolean true
#d-i partman-md/device_remove_md 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-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 partman-md/confirm boolean true
#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

### Clock and time zone setup

d-i clock-setup/utc boolean true
d-i time/zone string US/Pacific
d-i clock-setup/ntp boolean false

### Account setup

d-i passwd/root-login boolean false
d-i passwd/user-fullname string Partimus Admin
d-i passwd/username string partimus
d-i passwd/user-password-crypted password $1$nQudsnjbj$njk$jwKu/
d-i user-setup/allow-password-weak boolean true
d-i user-setup/encrypt-home boolean false

### Boot loader installation

d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true
d-i grub-installer/password-crypted password $1$byneYy.ndsjknFFF.

### Udeb

apt-setup-udeb apt-setup/services-select multiselect security
pkgsel pkgsel/update-policy select none

d-i user-setup/allow-password-weak boolean true
user-setup-udeb user-setup/password-weak boolean true
user-setup-udeb user-setup/password-weak boolean true
user-setup-udeb user-setup/encrypted-private boolean false
user-setup-udeb user-setup/encrypt-home boolean false

### Package selection

tasksel tasksel/first multiselect lubuntu-desktop

### Installtime scripting

d-i preseed/late_command string \
wget -q -O /target/tmp/ ; \
in-target bash /tmp/

### Finishing up the first stage install

#d-i finish-install/reboot_in_progress note


In addition to the debian-installer directives specified in the preseed file, we can run custom shell scripts at the beginning of the install or near the end. The following is the Post-Install script we use at Partimus:


## establish local sources for packaging
# back up the default apt sources list

cp /etc/apt/sources.list /etc/apt/sources.list.bak

# place the following repository addresses in /etc/apt/sources.list

cat > /etc/apt/sources.list <<APT-SOURCES
deb bionic main restricted universe multiverse
deb bionic-updates main restricted universe multiverse
deb bionic-security main restricted universe multiverse
#deb bionic main-backports restricted universe multiverse
deb bionic partner

apt-get -y update

# a package group we’re going to install (ubuntu-restricted-extras) contains a package (ttf-corefonts) with interactive questions we want to 

echo -n 'ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula boolean true' | debconf-set-selections

# if we wish to store the files that this package will fetch from source forge locally, we can specify that URI.
#echo -n 'ttf-mscorefonts-installer msttcorefonts/dlurl string' | debconf-set-selections

# preseed debug tools
#apt-get -y install debconf-utils
#apt-get -y install vim 

## configure packages

# here we list packages we wish to add, and some default packages we don’t want to include

ADD_PACKAGES="lubuntu-restricted-extras libreoffice-writer libreoffice-calc libreoffice-impress gimp vim chromium-browser"

REM_PACKAGES="gnumeric gnumeric-common gnumeric-doc abiword abiword-common sylph simple-scan transmission-common transmission-gtk"

# we’re going to loop through and install these packages one at a time to avoid potential race conditions.

for p in $ADD_PACKAGES
apt-get -y install "$p"
sleep 5

for p in $REM_PACKAGES
apt-get -y remove "$p"
sleep 5

#enable guest account

cat %gt; /etc/lightdm/lightdm.conf.d/50-no-guest.conf <<LIGHTDM

## fix sources for placement in production
# when we place this system in the world, it’s not likely going to have access to our dedicated install server. We’ll rewrite our sources file to the global resources and update our APT cache.

cat > /etc/apt/sources.list <<APT-SOURCES
deb bionic main restricted universe multiverse
deb bionic-updates main restricted universe multiverse
deb bionic-security main restricted universe multiverse
#deb bionic partner
apt-get -y update

exit 0


Assuming our client machines are plugged into our installer subnet and configured to boot from the network, we’re now ready to boot our our network based unattended install.

DHCP configures endpoint addresses and tell them where to find the PXE environment.  The PXE config will tell our system where to find the installer files and our preseed configuration on our network.  The preseed file will configure our install and tell it where to fetch out custom post-install script.  The first time we provision a machine, it’s going to be a little slow as Apt-cacher will be busy fetching packages from Canonical over the Internet.  Subsequent installs from this install server will be much faster.  Alternatively, the apt-mirror package can be used, with a web server to host the files and the appropriate port configured for the sources.

Up next: How to set up an unattended install using a USB thumb drive.

Post a Comment

Your email is never published nor shared. Required fields are marked *