Unattended installation of Debian and Ubuntu 18.04


This post describes how to successfully perform an unattended installation of Debian and Ubuntu 18.04. Two methods are discussed, namely PXE boot and modifying the network installation ISO. In both cases I found through trial and error that adding the preseed configuration to the initrd of the installer was the most fool-proof way of getting it to work with zero interaction required.


  • Both the PXE and network installation ISO boot a kernel with an initrd that contains the installation programs.
  • The bootloader used is Syslinux.
  • Protip: this method also works if you want to generate a Ubuntu or Debian VM with QEMU.
  • In my opinion, both the documentation and implementation regarding fully unattended installation could be a lot better. Sadly alternative solutions like Kickstart don’t really solve either of these problems.

Some annoyances:

  • I’ve never been able to successfully load a fully unattended preseed configuration via any other means, such as loading it over TFTP during a network boot.
  • There is no “just use the default” setting that would prevent new options (for newer OS versions) from asking new questions and thus blocking unattended installation.
  • You either have to know your network interface names beforehand, or trust that the installer picks the right one. There is no way to automatically try all/multiple network interfaces until one of them ends up with working access to a mirror. Life is easier with homogeneous hardware.
  • The installation process itself is really slow. It certainly would be nice if they simply offered a rootfs image that was easy to make bootable. (They sort of do with Ubuntu Base, but I’ve not tested it as installation medium yet. TODO!)
  • If you’ve made a mistake in the preseed configuration (e.g. the networking doesn’t work) the installer WILL NOT let you change the values, and will instead loop forever using the preconfigured value.

I guess Canonical is in the business of selling MAAS or Kickstart-based solutions these days instead of improving these things, so in the mean time I’ll continue doing things the hard way. I would still like to figure out how to perform these steps without actually modifying the initrd. Maybe some day…

Modifying the initrd

In order to perform a fully unattended installation, you can add preseed.cfg to the root directory of the initrd image. First, obtain the compressed initrd.gz file (see below) and unpack it:

mkdir preseeded
(cd preseeded && gzip -d < path/to/initrd.gz | cpio -id)

Add the preseed.cfg file and repack the initrd:

cp mypreseed.cfg preseeded/preseed.cfg
(cd preseeded && find . | cpio -o -H newC | gzip) > path/to/initrd.gz

That’s it. This method works for most of the installation media that exists for Debian and Ubuntu (e.g. netboot, hdmedia, cdrom)

Method 1: Minimal ISO

The first method modifies the minimal ISO. Note that this method does not work with the Kickstart-based Ubuntu desktop installation ISO.

Step one is to unpack the ISO using the tool of your choice:

7z x -omini mini.iso

Important file locations in the unpacked directory:

  • Initrd: mini/initrd.gz
  • Kernel: mini/linux
  • Syslinux config: mini/isolinux.cfg

Use the instructions from the previous section to modify and reconstruct the initrd. You can optionally replace the Syslinux config with a minimal configuration file listed in the “Final steps” section. When you’re done modifying the image, rebuild the modified ISO:

(cd mini && genisoimage -quiet -o ../mini-preseed.iso \
    -b isolinux.bin -c boot.cat -boot-info-table \
    -no-emul-boot -iso-level 2 -udf -rock \
    -J -l -D -N -joliet-long -relaxed-filenames \
    -allow-limited-size .)

You now have a preseeded, bootable ISO. Boot it using your favorite terrible server management tool like iDRAC or ILO!

Method 2: Network boot (PXE)

Obtain the netboot.tar.gz for your arch at http://cdimage.ubuntu.com/netboot/ or http://ftp.debian.org/debian/dists/stable/main/. Once unpacked, you can find your initrd at e.g. ubuntu-installer/$arch/initrd.gz. Modify it according to the instructions above.

The PXE boot payload tries to load the following Syslinux configuration files, in order:

  • pxelinux.cfg/$guid (not sure where the GUID comes from…)
  • pxelinux.cfg/01-xx-xx-xx-xx-xx-xx — where the xx part is the MAC address
  • pxelinux.cfg/AABBCCDD — hex encoding of the IP address that was assigned via DHCP.
  • pxelinux.cfg/default

The IP address lookup actually tries the full IP address first, and if that fails it tries again with one octet less. For example, for it would first try pxelinux.cfg/AC1E0453, then pxelinux.cfg/AC1E045, then pxelinux.cfg/AC1E04, and so on, until it reaches pxelinux.cfg/A.

You should replace the pxelinux.cfg/default file with the contents listed in the “Final steps” section if you’re only installing one machine (or if all machines have the same configuration), or alternatively create one configuration file per server using the aforementioned scheme.

Tip: You can easily script this to create multiple initrds and configurations, one for every machine you’re installing; ugly, but functional! The name of the initrd file is not relevant, as long as you specify the correct location in the Syslinux configuration.

I personally use dnsmasq as both DHCP and PXE/TFTP server, for example:


NB: using dnsmasq can be a bit annoying if your machines always boot using PXE since the installation process will automatically reboot the machine. Make sure you don’t end up creating an installation loop.

Final steps

By default, when booting the installation medium you’ll be greeted with a (Syslinux) menu where you can choose some installation parameters. You can bypass this by replacing isolinux.cfg with:

default install
label install
menu label ^Install
menu default
timeout 1
kernel linux
append initrd=initrd.gz console=ttyS0 pkgsel/language-pack-patterns= pkgsel/install-language-support=false vga=788 --- quiet

Note that the paths are different for the ISO and PXE! Make sure the kernel and initrd paths are correct, e.g. for PXE they should be relative to the TFTP root.

Example preseed configuration

You can find more information about these options online. Here’s an example that should work with Debian and Ubuntu 18.04:

# network settings
d-i netcfg/choose_interface select auto
#d-i netcfg/choose_interface select enp2s0f1
d-i netcfg/get_hostname string myhostname
d-i netcfg/get_domain string unassigned-domain
d-i netcfg/hostname string myhostname

# networking with DHCP:
d-i netcfg/disable_autoconfig boolean false

# networking with static IP:
#d-i netcfg/disable_autoconfig boolean true
#d-i netcfg/get_nameservers string
#d-i netcfg/get_ipaddress string
#d-i netcfg/get_netmask string
#d-i netcfg/get_gateway string
#d-i netcfg/confirm_static boolean true

# regional setting
d-i debian-installer/language string en_US:en
d-i debian-installer/country string US
d-i debian-installer/locale string en_US
d-i debian-installer/splash boolean false
d-i localechooser/supported-locales multiselect en_US.UTF-8
d-i pkgsel/install-language-support boolean true

# keyboard selection
d-i console-setup/ask_detect boolean false
d-i console-setup/layoutcode string us
d-i keyboard-configuration/modelcode string pc105
d-i keyboard-configuration/layoutcode string us
d-i keyboard-configuration/variantcode string intl
d-i keyboard-configuration/xkb-keymap select us(intl)
d-i debconf/language string en_US:en

# mirror settings
d-i mirror/country string manual
d-i mirror/http/hostname string archive.ubuntu.com
d-i mirror/http/directory string /ubuntu
d-i mirror/http/proxy string

# clock and timezone settings
d-i time/zone string Europe/Amsterdam
d-i clock-setup/utc boolean false
d-i clock-setup/ntp boolean true

# user account setup
d-i passwd/root-login boolean false
d-i passwd/root-password-crypted password $6$1AFDk6ogV1$hBdHKqLXkIg5fX8wJbxBiXFsL71s4ys9o2tP11X8vOU1yqn1p5XE/e1KPjBql5vPFzMLjihVnh52vGbcFQEIe1
d-i passwd/make-user boolean true
d-i passwd/user-fullname string RemoteControl
d-i passwd/username string rc
d-i passwd/user-password-crypted password $6$vGbcFQEIe2$/NIJEDEfoY.xtvJuZqaRbq26.PHCQ2zfPzfR7zVI.rzxEZFj350pDEomMm76AZiy/oTBdlQFOi/2T6toPd2CS1
d-i passwd/user-uid string
d-i user-setup/allow-password-weak boolean true
#d-i passwd/user-default-groups string adm cdrom dialout lpadmin plugdev sambashare
d-i user-setup/encrypt-home boolean false

# configure apt, and install sshd
d-i apt-setup/restricted boolean true
d-i apt-setup/universe boolean true
d-i apt-setup/backports boolean true
# For Debian, use:
#d-i apt-setup/non-free boolean false
#d-i apt-setup/contrib boolean false
d-i apt-setup/services-select multiselect security
d-i apt-setup/security_host string security.ubuntu.com
d-i apt-setup/security_path string /ubuntu
tasksel tasksel/first multiselect openssh-server
d-i pkgsel/upgrade select safe-upgrade
d-i pkgsel/update-policy select none
d-i pkgsel/updatedb boolean true
# Debian:
d-i popularity-contest/participate boolean false

# disk partitioning
# TIP: you can comment all of this out and do only this step manually.
# More complex recipes are also possible.
d-i partman-auto/choose_recipe select atomic
d-i partman/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm_nooverwrite boolean true
d-i partman/confirm boolean true
d-i partman-auto/purge_lvm_from_device boolean true
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-lvm/no_boot boolean true
d-i partman-md/device_remove_md boolean true
d-i partman-md/confirm boolean true
d-i partman-md/confirm_nooverwrite boolean true
d-i partman-auto/method string regular
d-i partman-auto-lvm/guided_size string max
d-i partman-partitioning/confirm_write_new_label boolean true

# grub boot loader
d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true

# finish installation
d-i finish-install/reboot_in_progress note
d-i finish-install/keep-consoles boolean false
d-i cdrom-detect/eject boolean true
d-i debian-installer/exit/halt boolean false
d-i debian-installer/exit/poweroff boolean false

# some extra commands to set up SSH key access
d-i preseed/late_command string in-target mkdir /home/rc/.ssh ; \
                                in-target sh -c 'echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQxFvvaVQpz/hmRSksWXh8hjYIIlfOUrnIncPH+kzmv car@cdr" >> /home/rc/.ssh/authorized_keys' ; \
                                in-target chown -R rc:rc /home/rc/.ssh ; \
                                in-target sh -c 'echo "rc ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/rc' ; \
                                in-target systemctl enable serial-getty@ttyS0.service ;