Marc Wäckerlin
Für eine libertäre Gesellschaft

Manual Ubuntu Installation from Scratch

November 14, 2025

Views: 70

Because the Ubuntu-Server installation does not offer the device configuration (see Multi-Disk Encryption Magic), I need to install Ubuntu manually from scratch. This is also a good training to understand what magic the Ubuntu installer does. Here my hard disk configuration is already done according to Multi-Disk Encryption Magic, so this blog does not cover device setup. I just expect you have partitions ready to mount:

  • one disk (hdd/ssd) prepared as boot device (if not, see below)
  • one partition for /boot
  • one partition for / (root)
  • only for UEFI devices: one partition to mount to /boot/uefi
  • optional: a swap partition

The partitions in /boot and below must not be encrypted, all other partitions can be encrypted, and in fact I recommend to encrypt them.

Boot the Installation Environment

Just download the latest Ubuntu Desktop ISO image and copy it to an installation USB-stick. Then boot your new computer from that stick. When the installer starts, choose your language and keyboard-layout, connect to the network, then choose Try Ubuntu to stop the installer.

On the left upper side of the desktop, there is a button to get the launch pad. Click on the launcher with the Ubuntu logo on the very bottom of the launch pad and start a console from there.

Remote Installation

Optionally you can run everything from remote using SSH. The advantage of this is, that you can sit on your normal computer with your favorite environment, instead of doing everything from the limited installer desktop.

For this, just install the SSH server and set a password for the default user named ubuntu:

sudo apt-get update
sudo apt-get install -y openssh-server
passwd

Check the IP address of the device you want to install. You find it from the network settings, just open the settings from the Ubuntu launcher. In this example, let’s say it is 192.168.0.8.

On your favorite computer, which must be in the same local network, open a console and run:

ssh -o PubkeyAuthentication=no ubuntu@null192.168.0.8

Now you’re ready for remote installation.

Ubuntu Installation

The whole installation is done as root. Be careful, be aware that you could damage anything. Get root:

sudo -Hi

Definitions

Let’s define some variables that will then be used in the further instructions, adapt and define these variables ccording to your system:

# Ubuntu Version to install, e.g. Noble 24.04
export UBUNTU_VERSION=noble
# Ubuntu Base Package, availabilty may depend on the Ubuntu Version, e.g.
#   On Server Systems:  ubuntu-server, ubuntu-server-minimal
#   On Console Systems: ubuntu-standard, ubuntu-base, ubuntu-minimal
#   On Desktop Systems: ubuntu-desktop, ubuntu-dektop-minimal, kubuntu-desktop, ...
BASE_PACKAGE=ubuntu-minimal
# Name of a User on your System, typically your name (e.g. for me: marc)
export USERNAME=ubuntu
# Disks and Partitions (depending on your system's disks)
# Assumption in the setup below:
#   $BOOT_DISK and $EFI_DISK are partition 2 and 1 on $MBR_DISK
export CRYPT_DISK=/dev/big/big   # disk to decrypt on boot
export SWAP_DISK=/dev/crypt/swap # disk for swap
export ROOT_DISK=/dev/crypt/root # disk for root filesystem on /
export MBR_DISK=/dev/sdd         # boot disk
export BOOT_DISK=${MBR_DISK}2    # second partition on boot disk mounted to /boot
export EFI_DISK=${MBR_DISK}1     # first partition on boot disk mounted to /boot/efi
# Hostname (should be the same as in your DNS if you have a DNS)
export HOSTNAME=server01
# System Language, e.g. Switzerland / German
export LANG=de_CH.UTF-8
# Keyboard Layout (e.g. Switzerland / Default = German)
export XKBMODEL="pc105"
export XKBLAYOUT="ch"
export XKBVARIANT=""
export XKBOPTIONS=""

Important: Change and set these variables according to your system!

Prepare Target Filesystems

Make sure, all partitions are ready, e.g. open encrypted filesystems. In my case, this is: cryptsetup open /dev/big/big crypt, which opens /dev/big/big in /dev/crypt/root, but this may be different in your setting. The LVMs are the automatically ready. In this example, I install:

cryptsetup open $CRYPT_DISK crypt
  • swap on $SWAP_DISK (/dev/crypt/swap)
  • / on $ROOT_DISK (/dev/crypt/root)
  • /boot on $BOOT_DISK (/dev/sdd2)
  • BIOS boot / EFI in $EFI_DISK (/dev/sdd1)
  • MBR (Master Boot Record) on $MBR_DISK (/dev/sdd)
  • no uefi (very old server from 2014)

Be aware that device names may change, i.e. those in /dev/sd*, so you will need to add the UUIDs to your fstab and crypttab configuration, not the devices as they appear during the installation.

Prepare Boot Drive

BIOS (Legacy)-Boot, GPT-Disk

Without UEFI, you need a special 1MB BIOS boot partition. I’ll boot from $MBR_DISK (/dev/sdd), so I create it in $EFI_DISK (/dev/sdd1). The following steps destroy the device $MBR_DISK (/dev/sdd) and all it’s content. The following steps create a new GPT label, which destroys all existing partitions, then creates and initializes a BIOS boot partition as first partition, so partition 1 in $EFI_DISK (/dev/sdd1):

parted $MBR_DISK mklabel gpt
parted $MBR_DISK mkpart biosboot 1MiB 2MiB
parted $MBR_DISK set 1 bios_grub on
UEFI Boot Setup

On modern UEFI based systems, you need an EFI partition (FAT32, ca. 512MB with esp flag). I’ll boot from $MBR_DISK (/dev/sdd), so I create it in $EFI_DISK (/dev/sdd1). The following steps destroy the device $MBR_DISK (/dev/sdd) and all it’s content. The following steps create a new GPT label, which destroys all existing partitions, then creates and initializes an EFI partition as first partition, so partition 1 in /dev/sdd1:

parted $MBR_DISK mklabel gpt
parted $MBR_DISK mkpart ESP fat32 1MiB 513MiB
parted $MBR_DISK set 1 esp on
mkfs.vfat -F32 $EFI_DISK
Boot Partition

All systems, legacy and UEFI, need a boot partition. I create it as $BOOT_DISK (/dev/sdd2) with ext4 file system on the same disk I prepared above. Again, this destroys previous data on the disk:

parted $MBR_DISK mkpart boot ext4 2MiB 100%
mkfs.ext4 $BOOT_DISK

Root Partition and Swap

I always use a large root partition without further splitting up, so I format (destroy) my device on $ROOT_DISK (/dev/crypt/root) and initialize swap on $SWAP_DISK (/dev/crypt/swap):

mkfs.ext4 $ROOT_DISK
mkswap $SWAP_DISK

Mount Filesystems

Now everything is ready to be mounted to /mnt (you can mount it wherever you want, /mnt is somehow a standard place). On legacy non UEFI systems, skip the last two steps to mount /boot/efi. It is important that you first mount the upper paths, then the lower paths:

mount $ROOT_DISK /mnt
mkdir /mnt/boot
mount $BOOT_DISK /mnt/boot
mkdir /mnt/boot/efi              # on UEFI BIOS only
mount $EFI_DISK /mnt/boot/efi    # on UEFI BIOS only

Bootstrap Minimal Ubuntu Base System

There is a script debootstrap that downloads and installs a minimal debian system into a directory. All you need to specify is the release code name (e.g. noble for Ubuntu 24.04 LTS) and the target directory:

apt-get update
apt-get install -y debootstrap
debootstrap $UBUNTU_VERSION /mnt

Switch to New Ubuntu

Now we can chroot to the new system:

mount --bind /dev /mnt/dev
mount --bind /proc /mnt/proc
mount --bind /sys /mnt/sys
chroot /mnt

All we do now is applied to the new system that we setup.

Language, Localisation

To setup any localisation, e.g. German (de) in Switzerland (CH), save it to /etc/default/locale and generate the corresponding locales:

echo "LANG=$LANG" > /etc/default/locale
locale-gen $LANG

Keyboard

Configure the keyboard used on the local console, e.g. as Swiss-German keyboard setup:

apt install -y keyboard-configuration console-setup
cat > /etc/default/keyboard <<EOF
XKBMODEL="$XKBMODEL"
XKBLAYOUT="$XKBLAYOUT"
XKBVARIANT="$XKBVARIANT"
XKBOPTIONS="$XKBOPTIONS"
EOF
setupcon

Host Name

To setup a host name, just store it in /etc/hostname and for routing, add it to /etc/hosts:

echo $HOSTNAME > /etc/hostname
echo "127.0.1.1 $HOSTNAME" >> /etc/hosts

Create a User

If you want to be able to login after your computer restarts, you must create a user on your name. And if you want to execute administration tasks from that user, you need to give it root access through sudo by adding it to the sudo group, defined in variable $USERNAME above. This also sets a password to login into the new system:

adduser $USERNAME

Then allow this user to execute root privileges with sudo:

usermod -aG sudo $USERNAME

Configure APT Package Sources

Simple

Typically you want all Ubuntu sources, so add them, the simplest way is to use apt-add-repository:

apt-get install -y software-properties-common
add-apt-repository -y main
add-apt-repository -y universe
add-apt-repository -y multiverse
add-apt-repository -y restricted
add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu $(lsb_release -cs)-updates main restricted universe multiverse"
add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu $(lsb_release -cs)-security main restricted universe multiverse"
add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu $(lsb_release -cs)-backports main restricted universe multiverse"

Manually

Alternatively you can install everything manually without dependency on software-properties-common by writing into /etc/apt/sources.list:

CODENAME=$(lsb_release -cs)
cat > /etc/apt/sources.list <<EOF
deb http://archive.ubuntu.com/ubuntu $CODENAME main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu $CODENAME-updates main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu $CODENAME-security main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu $CODENAME-backports main restricted universe multiverse
EOF

Update and Upgrade

Upgrade system to the latest package versions:

apt-get update
apt-get -y dist-upgrade

Install Necessary System Packages

You may need the following packages:

  • Base System as defined in $BASE_PACKAGE, choose your flavour, i.e. one of e.g.:
    • Server Systems:
      • ubuntu-server
      • ubuntu-server-minimal
    • Console Systems:
      • ubuntu-standard
      • ubuntu-base
      • ubuntu-minimal
    • Desktop Systems:
      • ubuntu-desktop
      • ubuntu-dektop-minimal
      • kubuntu-desktop
  • for LVM: lvm2
  • for encrypted boot disks: cryptsetup-initramfs
  • to allow SSH access: openssh-server
  • and always: linux-image-generic grub-pc systemd-sysv

Example:

apt-get install -y $BASE_PACKAGE linux-image-generic grub-pc \
               lvm2 cryptsetup-initramfs \
               openssh-server

Encrypted Filesystems in CryptTab

To be able to mount encrypted filesystems at boot, you must enter them in /etc/crypttab. In my example, the root device /dev/big/big is encrypted to $ROOT_DISK (/dev/crypt/root). First I need it’s LUKS-UUID: cryptsetup luksUUID /dev/big/big, this is how I reference it in my /etc/crypttab. Be sure to always use UUIDs, unless you’re absolutely sure, the name you refer to will never change.

You can use vi or install vim to edit /etc/crypttab, or if it is just one device, you can use this script, just adapt DEVICE in the first line to your needs:

LUUID=$(cryptsetup luksUUID $CRYPT_DISK)
cat > /etc/crypttab <<EOF
crypt UUID=$LUUID none luks,discard,initramfs
EOF

Verify that the file content makes sense and UUID is set correctly, e.g. cat /etc/crypttab results in my system to:

crypt UUID=eb3e94f0-2d76-4e92-b3ce-0e2911d9448e none luks,discard,initramfs

All Filesystems in FSTab

You must add all filesystems you’ll need in /etc/fstab, including pseudo filesystems, here again, you may use an editor to set it up, or you can use cat, which uses the initually defined variables. Again, don’t add the EFI part if you are on a legacy system and don’t add the SWAP part if you don’t have a swap partition:

cat > /etc/fstab <<EOF
proc                                      /proc     proc   defaults       0 0
sysfs                                     /sys      sysfs  defaults       0 0
devpts                                    /dev/pts  devpts gid=5,mode=620 0 0
tmpfs                                     /run      tmpfs  defaults       0 0
tmpfs                                     /tmp      tmpfs  defaults       0 0
UUID=$(blkid -s UUID -o value $ROOT_DISK) /         ext4   defaults       0 1
UUID=$(blkid -s UUID -o value $BOOT_DISK) /boot     ext4   defaults       0 2
UUID=$(blkid -s UUID -o value $EFI_DISK)                            /boot/efi vfat   defaults       0 2
UUID=$(blkid -s UUID -o value $SWAP_DISK) none      swap   sw             0 0
EOF

On my legacy system, this results in:

proc                                      /proc     proc   defaults       0 0
sysfs                                     /sys      sysfs  defaults       0 0
devpts                                    /dev/pts  devpts gid=5,mode=620 0 0
tmpfs                                     /run      tmpfs  defaults       0 0
tmpfs                                     /tmp      tmpfs  defaults       0 0
UUID=442880bf-1b95-4b53-8c48-7e64d787b0d0 /         ext4   defaults       0 1
UUID=4e077e0e-bf0d-405e-b1be-19c6e013ee75 /boot     ext4   defaults       0 2
UUID=72e6dea6-d286-4427-8e56-74ba8f779fa8 none      swap   sw             0 0

Configure DHCP Network

Netplan and the Resolver require some packages, either libnss-resolve (old Ubuntu, e.g. focal) or systemd-resolved (new Ubuntu, e.g. noble):

apt-get install -y netplan.io systemd-sysv systemd-resolved

DNS Resolver

For DNS-Resolver, the hosts: line in file /etc/nsswitch.conf must contain resolve as hosts: files resolve [!UNAVAIL=return] dns. You can edit it manually or with a script:

sed -i 's/^hosts:.*$/hosts:          files resolve [!UNAVAIL=return] dns/g' /etc/nsswitch.conf

NetPlan Configuration

To configure the network, you need to setup a netplan configuration. Use ip -br link to find all available network devices. Also here, you can create /etc/netplan/01-netcfg.yaml manually or with a script:

IFACES=$(ip -br link | awk '$2 ~ /UP|DOWN/ {print $1}')
cat > /etc/netplan/01-netcfg.yaml <<EOF
network:
  version: 2
  renderer: networkd
  ethernets:
$(
  for IFACE in $IFACES; do
    echo "    $IFACE:"
    echo "      dhcp4: true"
  done
)
EOF

On my server, there are two network interfaces eno1 and eno2, so the resulting file /etc/netplan/01-netcfg.yaml is:

network:
  version: 2
  renderer: networkd
  ethernets:
    eno1:
      dhcp4: true
    eno2:
      dhcp4: true

Setup InitRamFS and Kernel

Finally make sure our system boots and finds it’s kernel and initial filesystem. First of all, if you have LVm and encrypted filesystems and if you need to enter the password at boot time, this must be enabled:

echo "CRYPTSETUP=y" > /etc/cryptsetup-initramfs/conf-hook
echo "ASKPASS=y" >> /etc/cryptsetup-initramfs/conf-hook
echo "LVM=yes" > /etc/initramfs-tools/conf.d/lvm

Finally initialize GRUB and the initial RAM filesystem.

Legacy Systems

For those on legacy systems without EFI. In my case, I install GRUB on /dev/sdd, this may be different on your system.

update-initramfs -u -k all
update-grub
grub-install $MBR_DISK

EFI Systems

For those on modern EFI systems:

apt install -y grub-efi-amd64 shim-signed efibootmgr
update-initramfs -u -k all
update-grub
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu

Done

You’re done! Just reboot and make sure you unplug your temporary Ubuntu boot device before restarting:

exit
reboot

Hint: For those who used SSH during the installation, the key now has changed and you’ll need to remove the old SSH key of this machine. That’s not a bug nor an issue. So here: ssh-keygen -f ~/.ssh/known_hosts -R 192.168.0.8

comments title