Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

How to install Debian encrypted with LVM on a NVMe software RAID

profile picture
Author
Christian Külker
Published
2025-10-07
Time to read
11 minutes reading time

Introduction

Hetzner's Rescue System includes the installimage tool, which automates installing popular Linux distributions on dedicated servers.

This tutorial shows how to use installimage to install Debian with full-disk encryption on two NVMe drives using a software RAID with LVM.

MD RAID ➔ LUKS ➔ LVM ➔ filesystem

In addition remote unlocking via SSH (dropbear) embedded in the initramfs, with the initramfs stored on a separate unencrypted /boot partition is explained. The steps were tested on an AX41-NVMe dedicated server with Debian 11.1 Bullseye and Debian 13.0 Trixie. It should work with Debian 12.12 Bookworm.

Prerequisites

  • Hetzner account
  • Server with IPv4 address booted into the rescue system
  • RSA or ECDSA/ED25519 SSH public key (can be created on the fly)
  • No private networks attached on Hetzner Cloud

Step 1 - Create or copy SSH public key

In order to remotely unlock the encrypted disk at least one SSH key is required. This key will also be used to later log in to the booted system. The dropbear SSH daemon (version 2020.81-3) included in Debian 11.1 supports Rivest–Shamir–Adleman (RSA) and elliptic curve keys, so does 2025.88-2 in Debian 13.0. If you do not have such a key, you need to generate one. This can be the case if a password is used to access the rescue system. In case you already have such a key in ~/.ssh/{authorized,robot_user}_keys, continue with step 2. (The installimage script can use an additional robot_user_keys file for installation keys.)

For example, to generate ed25519 (a non National Institute of Standards and Technology (NIST) elliptic-curve) SSH key run:

ssh-keygen -t ed25519

Copy the public key to the rescue system and add it to existing keys.

cat id_ed25519.pub|ssh root@<your-host> -T 'cat>>/root/.ssh/authorized_keys'
cat id_ed25519.pub|ssh root@<your-host> -T 'cat>>/root/.ssh/robot_user_keys'

Step 2 - Create or copy installimage configuration file

When installimage is called without any options, it will search for the file /autosetup. If it does not find it, it starts in interactive mode and will open an editor after a distribution image has been selected. On exiting the editor, the installation will proceed and the corresponding configuration is saved as /installimage.conf on the filesystem of the installed system. In this tutorial we will use such a configuration to install directly via the /autosetup file.

Create the file /autosetup with the following content or copy it to the server running the rescue system.

Note 1: Replace <secret> with a secure password and adjust drive names and partitioning as needed. Usually Linux Unified Key Setup (LUKS) via cryptsetup is configured for a maximum password length of 512 characters (the maximum length was not verified for this tutorial).

Note 2: If installing in UEFI mode, you must add PART /boot/efi esp 256M. This tutorial installed Debian 13 without UEFI. However this might be considered for other OSes, different hardware or future version of Debian.

Note 3: To understand what image file names are available, see https://download.hetzner.com/bootimages/ and use credentials provided via e-mail or use the installimage script until you see the image name you would like to use (press ESC and then choose 'exit' to quit). The usage and creation of custom images is not covered in this guide. Be aware that the naming convention for Debian versions is to omit the first zero. It is Debian 13.0 for example, however the corresponding Hetzner image is Debian-1300...

CRYPTPASSWORD <secret>

SWRAID 1
SWRAIDLEVEL 1
DRIVE1 /dev/nvme0n1
DRIVE2 /dev/nvme1n1

#PART  /boot/efi  esp    256M # Only needed in UEFI mode, see note 2 above
PART  /boot  ext4    2G
PART  lvm    vg0    all crypt

LV  vg0  swap  swap  swap 32G
LV  vg0  root  /     xfs  all

BOOTLOADER grub

HOSTNAME <host.example.com>

# For custom images:
# IMAGEPATH
# IMAGE
# For Hetzner images:
# IMAGE /root/images/Debian-1107-bullseye-amd64-base.tar.gz
# IMAGE /root/images/Debian-1208-bookworm-amd64-base.tar.gz
IMAGE /root/images/Debian-1300-trixie-amd64-base.tar.gz

SSHKEYS_URL /root/.ssh/authorized_keys

This example configures 32GB of swap, 2GB for /boot and uses the xfs filesystem (better for large files). Other values or filesystems, like ext4 are possible.

Step 3 - Create or copy post-install script

In order to remotely unlock the encrypted partition, we need to install and add the dropbear SSH server to the initramfs which is stored on the unencrypted /boot partition. This will also trigger the inclusion of dhclient to configure networking, but without any extras. To enable support for Hetzner Cloud, we need to add a hook which includes support for RFC3442 routes.

In order to run these additional steps we need a post-install script for installimage.

Create a file /tmp/post-install.sh on the rescue system with the following content:

#!/bin/bash

add_rfc3442_hook() {
  cat << EOF > /etc/initramfs-tools/hooks/add-rfc3442-dhclient-hook
#!/bin/sh

PREREQ=""

prereqs()
{
        echo "\$PREREQ"
}

case \$1 in
prereqs)
        prereqs
        exit 0
        ;;
esac

if [ ! -x /sbin/dhclient ]; then
        exit 0
fi

. /usr/share/initramfs-tools/scripts/functions
. /usr/share/initramfs-tools/hook-functions

mkdir -p \$DESTDIR/etc/dhcp/dhclient-exit-hooks.d/
cp -a /etc/dhcp/dhclient-exit-hooks.d/rfc3442-classless-routes \$DESTDIR/etc/dhcp/dhclient-exit-hooks.d/
EOF

  chmod +x /etc/initramfs-tools/hooks/add-rfc3442-dhclient-hook
}

# (1) Install hook
add_rfc3442_hook

# (2) Copy SSH keys for dropbear
#     Detect Debian version
VERSION_ID=$(grep VERSION_ID /etc/os-release | sed -e 's/.*"\(.*\)"/\1/')
echo "Detected Debian $VERSION_ID"

case "$VERSION_ID" in
  11|12)
    echo "Copy authorized_keys to /etc/dropbear-initramfs/authorized_keys"
    mkdir -p /etc/dropbear-initramfs
    cp -a /root/.ssh/authorized_keys /etc/dropbear-initramfs/authorized_keys
    ;;
  13)
    echo "Copy authorized_keys to /etc/dropbear/initramfs/authorized_keys"
    mkdir -p /etc/dropbear/initramfs
    cp -a /root/.ssh/authorized_keys /etc/dropbear/initramfs/authorized_keys
    ;;
  *)
    echo "WARNING: Unknown Debian version ($VERSION_ID)!"
    echo "(You will not be able to log in)"
    exit 2
    ;;
esac

# (3) Update system
apt-get update >/dev/null
apt-get -y install cryptsetup-initramfs dropbear-initramfs

Important note: make the post-install script executable:

chmod +x /tmp/post-install.sh

Step 4 - Start installation

Before starting the installation, check again the content of the following files:

  • /root/.ssh/authorized_keys - our public SSH key(s) (RSA or ECDSA/ed25519)
  • /autosetup - installimage configuration
  • /tmp/post-install.sh - is executable and contains the post-install script

Now we are ready to start the installation with the following command:

installimage -x /tmp/post-install.sh

If you want to capture parts of the visible output or measure the time, you can use this command instead:

time installimage -x /tmp/post-install.sh |tee -a /root/my-install.log

Wait until the installation completes and check the /root/debug.txt and the screen output (or my-install.log) for any errors. For Debian 13 the installation takes less than 2 minutes.

In the screen output the following 2 lines show a successful configuration, in this case Debian 13:

Detected Debian 13
Copy authorized_keys to /etc/dropbear/initramfs/authorized_keys

Step 5 - Boot installed system

After the installation has finished and any errors are resolved (see section debugging below in case), we can run reboot to restart the server and boot the newly installed initramfs. We can watch the boot process if we have a KVM attached or via remote console on a cloud instance.

After some time the server should respond to ping. Now log in via SSH into BusyBox initramfs shell provided by dropbear and run the command cryptroot-unlock to unlock the encrypted partition(s) from the BusyBox command line.

ssh -i /root/.ssh/id_ed25519 root@<your-host>

The authenticity of host '<your-host> (<your-host>)' can't be established.
ECDSA key fingerprint is SHA256:uY/i2eu8jee8RB9aWwapQquSO+G6E/VMorWQ1fA9clM.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '<your-host>' (ECDSA) to the list of known hosts.
To unlock root partition, and maybe others like swap, run `cryptroot-unlock`.


BusyBox v1.37.0 (Debian 1:1.37.0-6+b3) built-in shell (ash)
Enter 'help' for a list of built-in commands.

~ # cryptroot-unlock
Please unlock disk luks-53152dbf-856e-4e13-b09f-8d05f01a43bc:
cryptsetup: luks-53152dbf-856e-4e13-b09f-8d05f01a43bc set up successfully
~ # Connection to <your-host> closed by remote host.
Connection to <your-host> closed.

If the password is correct the boot from the initramfs will continue to the new installed system. Before the new system is up and running, we will automatically be disconnected from the temporary SSH session of the initramfs.

After a few seconds we can log in to our new system via SSH into. Be aware that if we use the same name to connect to the initramfs of BusyBox via SSH (dropbear) and to the booted system, SSH will give warnings about a changed host name. It is advised to use different names. Either by using IP and name or by setting up two different DNS names for the same IP. By doing this we keep the SSH function of warning if the booted server host (Debian) fingerprint changes.

Step 6 - Verification (Optional)

The correct installation can be verified by lsblk -o NAME,FSTYPE,SIZE,MOUNTPOINT either from the rescue system or from the booted system. The hierarchy of NVMe ➔ MD (RAID) ➔ LUKS ➔ LVM ➔ XFS is clearly visible:

lsblk -o NAME,FSTYPE,SIZE,MOUNTPOINT
NAME                                            FSTYPE          SIZE MOUNTPOINT
nvme0n1                                                           476.9G
├─nvme0n1p1                                     linux_raid_member     2G
│ └─md0                                         ext4                  2G /boot
└─nvme0n1p2                                     linux_raid_member 474.9G
  └─md1                                         crypto_LUKS       474.8G
    └─luks-53152dbf-856e-4e13-b09f-8d05f01a43bc LVM2_member       474.8G
      ├─vg0-swap                                swap                 32G [SWAP]
      └─vg0-root                                xfs               442.8G /
nvme1n1                                                           476.9G
├─nvme1n1p1                                     linux_raid_member     2G
│ └─md0                                         ext4                  2G /boot
└─nvme1n1p2                                     linux_raid_member 474.9G
  └─md1                                         crypto_LUKS       474.8G
    └─luks-53152dbf-856e-4e13-b09f-8d05f01a43bc LVM2_member       474.8G
      ├─vg0-swap                                swap                 32G [SWAP]
      └─vg0-root                                xfs               442.8G /

Step 7 - Harden the system (Optional)

Writing the cryptsetup password inside the /autosetup file is very convenient. A copy of this file is placed on the installed root filesystem as /installimage.conf. And not in /root/debug.txt but in /installimage.debug we can find a line like Sent install.conf to statsserver: HTTP/2 201.

It is possible to update the cryptsetup password with a different one manually after the initial setup via /autosetup. The update procedure do not require to store the password in a text file.

First test which slot the cryptsetup password is stored. Usually it is slot 0. This can be done with cryptsetup --verbose open --test-passphrase <DEVICE>.

cryptsetup --verbose open --test-passphrase /dev/md1
Enter passphrase for /dev/md1: <secret>
Key slot 0 unlocked.
Command successful.

Usually this should give the same result, but will display other slots in the case more than one slot is used:

cryptsetup luksDump /dev/md1|grep luks2
  0: luks2

To change the cryptsetup password, set the password for slot 0 with:

cryptsetup luksChangeKey /dev/md1 -S 0
Enter LUKS passphrase to be changed:
Enter new LUKS passphrase:

Step 8 - Debugging (Optional)

The installimage is not idempotent in regard to software RAID. The devices /dev/md0 and /dev/md1 will exists after the first run. If we would run the installimage twice to reinstall the machine we will get errors like Device /dev/md/1 is in use. In this case remove and stop the software RAID before running installimage again. Also it might be advisable to delete mdadm meta data. If the meta data is not deleted, the old metadata will remain on the devices, but will appear older than the new metadata and so will usually be ignored. The old metadata can be removed by giving the option --zero-superblock. This was not tested for this tutorial. Note: Be careful to call --zero-superblock with clustered RAID, make sure the array isn't used or assembled in an other cluster node before execute it.

mdadm --remove /dev/md0
mdadm --remove /dev/md1
mdadm --stop /dev/md0
mdadm --stop /dev/md1

Conclusion

After configuration and executing installimage, booting into the initramfs, entering the cryptsetup password, the installed system is booted with a two-tier process and can be used.

While the boot or reboot of the system needs one additional step (the manual SSH session to the BusyBox initramfs) it adds a layer of security towards the storage subsystem. However an unattended reboot is not possible with this dropbear setup.

Credits

The tutorial for Debian 11 (install-debian-11-with-lvm-encrypted-nvme-software-raid) was inspired by How to install Ubuntu 20.04 with full disk encryption and parts (for example the cloud hook) had been copied under the MIT license at 2022-04-07.

This tutorial for Debian in general, tested with Debian 13, and hopefully for other future versions of Debian improves on the former Debian 11 tutorial with various changes to wording, typos, some changes to installation (for example adding robot_user_keys, changes of package names due to Debian 13) and minor other configuration changes (for example using 2GB for /boot, to add more space for kernels).

License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue

Discover our

Dedicated Servers

Configure your dream server. Top performance with an excellent connection at an unbeatable price!

Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more