DEV Community

Cover image for How to automatically decrypt a LUKS LVM setup on boot with a USB
Filis Futsarov
Filis Futsarov

Posted on • Originally published at filis.me

How to automatically decrypt a LUKS LVM setup on boot with a USB

This guide walks you through a robust procedure to auto-decrypt a LUKS-on-LVM setup at boot with a USB key. It assumes you already have your system set up with LUKS encryption on LVM.

The core idea is simple: keep a dedicated USB stick at home to unlock your system effortlessly, and leave it behind whenever you head out, so your machine stays securely encrypted on the go.

I extensively tested the procedure in multiple VMs and on real-world installs of Ubuntu 24.04 and Pop!_OS 22.04. Until reaching that point, I repeatedly broke my VM while trying outdated guides, AI-generated suggestions, and other unreliable sources—until I found my own way and then passed it to real setups. Here’s what I tried (and what finally worked with some customization):

  • ❌ initramfs hooks
  • ❌ udev scripts
  • ❌ dracut modules
  • ✅ cryptsetup using keyscript option (handled by initramfs-tools)

This solution relies on the keyscript option supported by initramfs-tools (not by cryptsetup itself), which allows delegating the decryption process to a custom script in a higher-level and safer way than using low-level initramfs hooks — although the option is defined in /etc/crypttab and passed to the initramfs layer, cryptsetup itself only accepts raw key data.

Internally, initramfs-tools handles keyscript by executing the specified script and piping its output to cryptsetup, effectively feeding the keyfile through standard input.

Most scripts I came across failed to handle this process in a fault-tolerant way: instead of falling back to the familiar "Enter password" screen when any kind of error happens, they leave you with a broken system.

That’s why I wrote a script that does things properly. It mounts your USB, attempts to load the keyfile up to three times, and if it still fails, gracefully falls back to the standard password prompt you're used to.

Before proceeding, ⚠️ make a full disk backup using Clonezilla (not just the partition) and test this solution in a VM running the same Ubuntu version as yours. This ensures you're familiar with the process and minimizes risk — I personally broke my VM several times during testing for writing this post, but snapshots made rollback easy.

The only requirement for this solution to work is support for the keyscript parameter, which has been available in initramfs-tools since at least Ubuntu 16.04.

✅ Tested on:

  • Ubuntu 22.04 and 24.04
  • Pop!_OS 22.04

These cover cryptsetup versions from 2.4.3 to 2.7.0. You can verify your version with:

cryptsetup --version
Enter fullscreen mode Exit fullscreen mode

1. Creating a secure keyfile

This command creates a 4MB keyfile filled with cryptographically secure binary data (non-ASCII characters), providing high entropy per byte — making brute-force attacks significantly harder.

💡 Tip: Save the keyfile directly to the USB drive that you'll use to decrypt your setup.

dd if=/dev/urandom of=/media/usb/secure.key bs=1M count=4 iflag=fullblock
Enter fullscreen mode Exit fullscreen mode

2. Downloading the unlock script

We'll need the unlock script for the keyscript parameter in the crypttab entry.

In systemd-based setups (e.g. Pop!_OS), the keyscript must be placed inside /lib/cryptsetup/scripts. In non-systemd setups (e.g. Ubuntu), it can technically reside anywhere — but placing it in /lib/cryptsetup/scripts ensures compatibility across systems.

sudo wget -O /lib/cryptsetup/scripts/keyscript.sh \
  https://raw.githubusercontent.com/filisko/cryptsetup-usb-keyscript/main/src/keyscript.sh
Enter fullscreen mode Exit fullscreen mode

⚠️ It's critical that the script is owned by root and has execution permissions:

sudo chown root:root /lib/cryptsetup/scripts/keyscript.sh
sudo chmod 755 /lib/cryptsetup/scripts/keyscript.sh
Enter fullscreen mode Exit fullscreen mode

3. Finding your USB's UUID

We need USB's UUID to tell cryptsetup where to find the keyfile.

sudo blkid
Enter fullscreen mode Exit fullscreen mode

There will be many lines like the following one. We're interested in the UUID="81D6-413D" part.

/dev/sda1: UUID="81D6-413D" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="c371356e-01"
Enter fullscreen mode Exit fullscreen mode

3. Updating /etc/crypttab entries

To enable automatic decryption, we need to edit /etc/crypttab and provide:

  • The LUKS device name (already there).
  • The UUID of the LUKS-encrypted root partition (already there).
  • The full keyfile path, using the USB device's UUID: /dev/disk/by-uuid/81D6-413D:/secure.key (must be set).
  • The necessary options for cryptsetup to invoke the keyscript (must be set).

This tells the system where to find the keyfile and how to decrypt at boot.

sudo nano /etc/crypttab
Enter fullscreen mode Exit fullscreen mode

It’s very likely that your system has an entry like this by default (note luks):

dm_crypt-0 UUID=XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX none luks
Enter fullscreen mode Exit fullscreen mode

I suggest that you save it somewhere or comment it putting # at the beginning of the line.

The first thing that you see in the line is LUKS' device name that we will also need later to add the keyfile inside, so also save it. Common names are:

  • dm_crypt-0 / cryptdrive (Ubuntu)
  • cryptdata (Pop!_OS)

⚠️ It's crucial to preserve the original device name (e.g.: dm_crypt-0), UUID, and exact field spacing to avoid boot issues.

Replace (not add, duplicate UUIDs or device names ºare not allowed) your entry to match the changes of this entry:

dm_crypt-0 UUID=XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX /dev/disk/by-uuid/81D6-413D:/secure.key luks,discard,cipher=aes-xts-plain64,size=256,hash=sha1,keyscript=/lib/cryptsetup/scripts/keyscript.sh,tries=4
Enter fullscreen mode Exit fullscreen mode

⚠️ The tries=4 option is mandatory — it ensures the system runs the script 3 times and gives it a chance to gracefully fall back on the 4th try. That 4th attempt is where the script prompts the user to manually enter the password, in case:

  • The USB isn't connected or could not be properly mounted.
  • The keyfile isn't found.
  • The keyfile is invalid.

Double-check the USB UUID and keyscript path, especially if you changed the script location.

4. Get LUKS' device path

With LUKS' device name (e.g.: dm_crypt-0) from the crypttab entry in previous step, we will get its device path to add the keyfile into it.

sudo cryptsetup status dm_crypt-0
Enter fullscreen mode Exit fullscreen mode

The line that we need from the output is: device: /dev/nvme0n1p3 where /dev/nvme0n1p3 is the device path that we need.

/dev/mapper/dm_crypt-0 is active and is in use.
 type:    LUKS2
 cipher:  aes-xts-plain64
 keysize: 512 bits
 key location: keyring
 device:  /dev/nvme0n1p3
 sector size:  512
 offset:  32768 sectors
 size:    1993975808 sectors
 mode:    read/write
Enter fullscreen mode Exit fullscreen mode

5. Add the keyfile to LUKS device

Here we need to set LUKS' device path that we got in the previous step and specify the keyfile that we generated at the very beginning, in step 1.

This will require an existing passphrase to add the new keyfile:

sudo cryptsetup luksAddKey /dev/nvme0n1p3 /media/usb/secure.key
Enter fullscreen mode Exit fullscreen mode

If "nothing happens" (it actually returns a shell success code) then it worked.

6. Test the keyfile

To test that the previous step was done correctly, we can do the following:

sudo cryptsetup luksOpen --test-passphrase --key-file /media/usb/secure.key /dev/nvme0n1p3
Enter fullscreen mode Exit fullscreen mode

If it works, it won't show anything (only return success in the terminal). If it fails, it will show:

No key available with this passphrase.
Enter fullscreen mode Exit fullscreen mode

7. Update initramfs

The last step to set everything up is updating initramfs. Initramfs is a temporary file system with a mini-Linux inside used only during boot. After boot everything is deleted.

Updating initramfs will add to the initramfs image the updated version of /etc/crypttab together with the unlock script that we've previously downloaded inside /lib/cryptsetup.

Warning: If you see any errors while running the command, it's better to revert to your original /etc/crypttab entry and post a comment in the comments section below this post.

This may take around 1 min:

sudo update-initramfs -u
Enter fullscreen mode Exit fullscreen mode

To check that our script was added inside initramfs we can run:

sudo lsinitramfs /boot/initrd.img-XXXXXXXX-generic | grep 'keyscript.sh'
Enter fullscreen mode Exit fullscreen mode

lsinitramfs lists the files of initramfs' compressed image. So all the files of the mini-Linux will be listed there.

And something like this should be printed in our console (you can Ctrl-C to cancel the grep after finding it):

usr/lib/cryptsetup/scripts/keyscript.sh
Enter fullscreen mode Exit fullscreen mode

8. Reboot

This is the last necessary step.

Simply reboot the USB and your setup should be automatically decrypted! 🎉🥳

sudo reboot
Enter fullscreen mode Exit fullscreen mode

If all went well, please comment below with your Ubuntu (or other distro) version and your cryptsetup version.

9. Logs

Something cool that I figured out is a way to log messages inside initramfs. Usually this is almost impossible because, as I said earlier, initramfs (a temporary filesystem) is cleaned up right after boot, so anything written anywhere will be removed.

I realized that logs can be sent directly to the kernel using /dev/kmsg, so that later on, after boot, you can grep logs doing the following:

sudo dmesg | grep 'keyscript.sh'
Enter fullscreen mode Exit fullscreen mode

This is pretty cool because either on success or failure (you had to manually enter the password) you can check the logs.

Logs when using the USB key normally:

[    8.199775] keyscript.sh: Attempt #1
[    8.213481] keyscript.sh: Using keyfile: /mnt/unlock-usb/secure.key
Enter fullscreen mode Exit fullscreen mode

Logs when USB device was not found or simply choosing to manually introduce the password

[    3.258277] keyscript.sh: Attempt #1
[    6.271922] keyscript.sh: Device for decryption not found: /dev/disk/by-uuid/81D6-413D
[    6.941412] keyscript.sh: Proceeding to ask for manually entering the password
Enter fullscreen mode Exit fullscreen mode

Logs when plugging in a USB without the keyfile

[    3.234567] keyscript.sh: Attempt #1
[    6.274412] keyscript.sh: Keyfile not found at: /mnt/unlock-usb/secure.key
[    6.954763] keyscript.sh: Proceeding to ask for manually entering the password
Enter fullscreen mode Exit fullscreen mode

Logs when the USB could not be mounted on boot

[    3.112245] keyscript.sh: Attempt #1
[    6.514122] keyscript.sh: Failed to mount device: /dev/disk/by-uuid/81D6-413D at /mnt/unlock-usb
[    6.613212] keyscript.sh: Proceeding to ask for manually entering the password
Enter fullscreen mode Exit fullscreen mode

Logs when the keyfile is located but incorrect

[    8.082461] keyscript.sh: Attempt #1
[    8.096964] keyscript.sh: Using keyfile: /mnt/unlock-usb/secure.key
[   13.501285] keyscript.sh: Attempt #2
[   13.511812] keyscript.sh: Using keyfile: /mnt/unlock-usb/secure.key
[   18.349567] keyscript.sh: Attempt #3
[   18.361704] keyscript.sh: Using keyfile: /mnt/unlock-usb/secure.key
[   23.205031] keyscript.sh: Max retries (3) reached. Proceeding to ask for manually entering the password.
Enter fullscreen mode Exit fullscreen mode

Share thoughts!

Would it be interesting to consider any USB as valid? Or it's better to restrict it by UUID as we're doing already?

Special thanks to Guilhem Moulin, from the official Cryptsetup Team at Debian, for helping me clear up some doubts about the behavior of cryptsetup.

Say thanks if you made it, or help me keep this post updated with new versions of Ubuntu or cryptsetup.

Top comments (0)