Encrypting a Debian install with UKI

Wed, 20 Aug 2025 15:45:15 -0400
crypto, debian, debian-planet, sysadmin, systemd

I originally setup a machine without any full disk encryption, then somehow regretted it quickly after. My original reasoning was that this was a "play" machine so I wanted as few restrictions on accessing the machine as possible, which meant removing passwords, mostly.

I actually ended up having a user password, but disabled the lock screen. Then I started using the device to manage my photo collection, and suddenly there was a lot of "confidential" information on the device that I didn't want to store in clear text anymore.

Pre-requisites

So, how does one convert an existing install from plain text to full disk encryption? One way is to backup to an external drive, re-partition everything and copy things back, but that's slow and boring. Besides, cryptsetup has a cryptsetup-reencrypt command, surely we can do this in place?

Having not set aside enough room for /boot, I briefly considered a "encrypted /boot" configuration and conversion (e.g. with this guide) but remembered grub's support for this is flaky, at best, so I figured I would try something else.

Here, I'm going to guide you through how I first converted from grub to systemd-boot then to UKI kernel, then re-encrypt my main partition.

Note that secureboot is disabled here, see further discussion below.

systemd-boot and Unified Kernel Image conversion

systemd folks have been developing UKI ("unified kernel image") to ship kernels. The way this works is the kernel and initrd (and UEFI boot stub) in a single portable executable that lives in the EFI partition, as opposed to /boot. This neatly solves my problem, because I already have such a clear-text partition and won't need to re-partition my disk to convert.

Debian has started some preliminary support for this. It's not default, but I found this guide from Vasudeva Kamath which was pretty complete. Since the guide assumes some previous configuration, I had to adapt it to my case.

Here's how I did the conversion to both systemd-boot and UKI, all at once. I could have perhaps done it one at a time, but doing both at once works fine.

Before your start, make sure secureboot is disabled, see the discussion below.

1. install systemd tools:

apt install systemd-ukify systemd-boot

2. Configure systemd-ukify, in /etc/kernel/install.conf:

layout=uki
initrd_generator=dracut
uki_generator=ukify

TODO: it doesn't look like this generates a initrd with dracut, do we care?

3. Configure the kernel boot arguments with the following in /etc/kernel/uki.conf:

[UKI]
Cmdline=@/etc/kernel/cmdline

The /etc/kernel/cmdline file doesn't actually exist here, and that's fine. Defaults are okay, as the image gets generated from your current /proc/cmdline. Check your /etc/default/grub and /proc/cmdline if you are unsure. You'll see the generated arguments in bootctl list below.

4. Build the image:

dpkg-reconfigure linux-image-$(uname -r)

5. Check the boot options:

bootctl list

Look for a Type #2 (.efi) entry for the kernel.

6. Reboot:

reboot

You can tell you have booted with systemd-boot because (a) you won't see grub and (b) the /proc/cmdline will reflect the configuration listed in bootctl list. In my case, a systemd.machine_id variable is set there, and not in grub (compare with /boot/grub/grub.cfg).

By default, the systemd-boot loader just boots, without a menu. You can force the menu to show up by un-commenting the timeout line in /boot/efit/loader/loader.conf, by hitting keys during boot (e.g. hitting "space" repeatedly), or by calling:

systemctl reboot --boot-loader-menu=0

See the systemd-boot(7) manual for details on that.

I did not go through the secureboot process, presumably I had already disabled secureboot. This is trickier: because one needs a "special key" to sign the UKI image, one would need the collaboration of debian.org to get this working out of the box with the keys shipped onboard most computers.

In other words, if you want to make this work with secureboot enabled on your computer, you'll need to figure out how to sign the generated images before rebooting here, because otherwise you will break your computer. Otherwise, follow the following guides:

Enabling Secure Boot with UKI on Debian

Disabling Lockdown Mode with Secure Boot on Distro Kernel

Signing the systemd-boot on Upgrade Using Dpkg Triggers

Re-encrypting root filesystem

Now that we have a way to boot an encrypted filesystem, we can switch to LUKS for our filesystem. Note that you can probably follow this guide if, somehow, you managed to make grub work with your LUKS setup, although as this guide shows, you'd need to downgrade the cryptographic algorithms, which seems like a bad tradeoff.

We're using cryptsetup-reencrypt for this which, amazingly, supports re-encrypting devices on the fly. The trick is it needs free space at the end of the partition for the LUKS header (which, I guess, makes it a footer), so we need to resize the filesystem to leave room for that, which is the trickiest bit.

This is a possibly destructive behavior. Be sure your backups are up to date, or be ready to lose all data on the device.

We assume 512 byte sectors here. Check your sector size with fdisk -l and adjust accordingly.

1. Before you perform the procedure, make sure requirements are installed:

apt install cryptsetup systemd-cryptsetup cryptsetup-initramfs

Note that this requires network access, of course.

2. Reboot in a live image, I like GRML but any Debian live image will work, possibly including the installer

3. First, calculate how many sectors to free up for the LUKS header

qalc> 32Mibyte / ( 512 byte )

  (32 mebibytes) / (512 bytes) = 65536

4. Find the sector sizes of the Linux partitions:

fdisk  -l /dev/nvme0n1 | awk '/filesystem/ { print $1 " " $4 }' |

For example, here's an example with a /boot and / filesystem:

$ sudo fdisk -l /dev/nvme0n1 | awk '/filesystem/ { print $1 " " $4 }'
/dev/nvme0n1p2 999424
/dev/nvme0n1p3 3904979087

5. Substract 1 from 2:

qalc> set precision 100
qalc> 3904979087 - 65536

Or, last step and this one, in one line:

fdisk -l /dev/nvme0n1 | awk '/filesystem/ { print $1 " " $4 - 65536 }'

6. Recheck filesystem:

e2fsck -f /dev/nvme0n1p2

7. Resize filesystem:

resize2fs /dev/nvme0n1p2 $(fdisk -l /dev/nvme0n1 | awk '/nvme0n1p2/ { print $4 - 65536 }')s

Notice the trailing s here: it makes resize2fs interpret the number as a 512 byte sector size, as opposed to the default (4k blocks).

8. Re-encrypt filesystem:

cryptsetup reencrypt --encrypt /dev/nvme0n1p2 --reduce-device-size=32M

This is it! This is the most important step! Make sure your laptop is plugged in and try not to interrupt it. This can, apparently, be resumed without problem, but I'd hate to show you how.

This will show progress information like:

Progress:   2.4% ETA 23m45s,      53GiB written, speed   1.3 GiB/s

Wait until the ETA has passed.

9. Open and mount the encrypted filesystem and mount the EFI system partition (ESP):

cryptsetup open /dev/nvme0n1p2 crypt
mount /dev/mapper/crypt /mnt
mount /dev/nvme0n1p1 /mnt/boot/efi

If this fails, now is the time to consider restoring from backups.

10. Enter the chroot

for fs in proc sys dev ; do
  mount --bind /$fs /mnt/$fs
done
chroot /mnt

Pro tip: this can be done in one step in GRML with:

grml-chroot /mnt bash

11. Generate a crypttab:

echo crypt_dev_nvme0n1p2 UUID=$(blkid -o value -s UUID /dev/nvme0n1p2) none luks,discard >> /etc/crypttab

12. Adjust root filesystem in /etc/fstab, make sure you have a line like this:

/dev/mapper/crypt_dev_nvme0n1p2 /               ext4    errors=remount-ro 0       1

If you were already using a UUID entry for this, there's nothing to change!

13. Configure the root filesystem in the initrd:

echo root=/dev/mapper/crypt_dev_nvme0n1p2 > /etc/kernel/cmdline

You might also want to look at the options: field in bootctl list to set the right thing here.

14. Regenerate UKI:

dpkg-reconfigure linux-image-$(uname -r)

Be careful here! systemd-boot inherits the command line from the system where it is generated, so this will possibly feature some unsupported commands from your boot environment. In my case GRML had a couple of those, which broke the boot. It's still possible to workaround this issue by tweaking the arguments at boot time, that said.

Also, the above will reconfigure the package named after the running kernel, it will only work if that's exactly the same version as the installed kernel.

15. Exit chroot and reboot

exit
reboot

Some of the ideas in this section were taken from this guide but was mostly rewritten to simplify the work. My guide also avoids the grub hacks or a specific initrd system (as the guide uses initramfs-tools and grub, while I, above, switched to dracut and systemd-boot). RHEL also has a similar guide, perhaps even better.

Somehow I have made this system without LVM at all, which simplifies things a bit (as I don't need to also resize the physical volume/volume groups), but if you have LVM, you need to tweak this to also resize the LVM bits. The RHEL guide has some information about this.