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

Written by Filis Futsarov
on June 25 of 2025
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 byinitramfs-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
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
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
⚠️ 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
3. Finding your USB's UUID
We need USB's UUID to tell cryptsetup
where to find the keyfile.
sudo blkid
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"
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
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
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
⚠️ 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
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
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
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
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.
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
To check that our script was added inside initramfs we can run:
sudo lsinitramfs /boot/initrd.img-XXXXXXXX-generic | grep 'keyscript.sh'
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
8. Reboot
This is the last necessary step.
Simply reboot the USB and your setup should be automatically decrypted! 🎉🥳
sudo reboot
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'
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
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
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
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
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.
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.