--- title: "How to Encrypt Data at Rest on Ubuntu 24.04 with LUKS" description: "Encrypt a data volume on Ubuntu 24.04 with LUKS2, unlock it at boot with a key file, move your database onto it and back up the LUKS header for recovery." category: "Security Compliance" language: en source: https://cubepath.com/docs/security-compliance/data-encryption-at-rest updated: 2026-09-25 --- # How to Encrypt Data at Rest on Ubuntu 24.04 with LUKS Encryption at rest protects stored data when someone gets hold of the storage itself: a decommissioned disk, a leaked volume snapshot or a stolen backup. On Linux, the standard tool is LUKS (Linux Unified Key Setup), managed with `cryptsetup`, which encrypts a whole block device so every file system, database and log written to it is encrypted transparently. In this tutorial you will encrypt a secondary data volume on Ubuntu 24.04 with LUKS2, mount it automatically at boot, move a MySQL data directory onto it, and set up the key and header backups you need to avoid losing the data. ## Prerequisites To follow this guide you need: - A server running Ubuntu 24.04 LTS, for example a CubePath VPS. - A non-root user with `sudo` privileges. - A second, empty disk or volume attached to the server. This guide uses `/dev/vdb`; your device name may differ. - Optionally, MySQL or MariaDB installed, for Step 6. > Warning: Formatting a device with LUKS destroys all data on it. Double check the device name in Step 2 before running `luksFormat`. ## Choosing where to encrypt There are several layers where you can encrypt data at rest. They protect against different things: | Layer | Tool | Protects against | Does not protect against | |-------|------|------------------|--------------------------| | Block device | LUKS / dm-crypt | Lost or reused disks, leaked volume snapshots | Anyone with access to the running, unlocked server | | Directory | fscrypt (ext4) | Other users reading a locked directory | Root on the running server | | Database | MySQL/MariaDB/PostgreSQL encryption features | Copies of raw database files | Database users with valid credentials | | Application | Encrypting fields before storing them | Database dumps and DB administrators | A compromised application server | | Files and backups | GnuPG, age, restic | Backups copied off the server | Whoever holds the key | LUKS is the right base layer for a server: it is built into the kernel, fast on CPUs with AES instructions and requires no application changes. Encrypt backups separately, since they leave the encrypted volume as plain files. ## Step 1 - Installing cryptsetup and checking hardware acceleration Install `cryptsetup`, which is usually already present on Ubuntu: ```bash sudo apt update sudo apt install cryptsetup ``` Check the version and whether your CPU supports AES instructions, which make encryption nearly free in performance terms: ```bash cryptsetup --version grep -m1 -o -w aes /proc/cpuinfo ``` ```output cryptsetup 2.7.0 aes ``` If the second command prints nothing, the CPU (or the virtual CPU model your VPS exposes) does not offer AES-NI. LUKS still works but uses more CPU. You can compare cipher speeds with a quick benchmark: ```bash cryptsetup benchmark --cipher aes-xts-plain64 ``` ```output # Tests are approximate using memory only (no storage IO). # Algorithm | Key | Encryption | Decryption aes-xts 512b 2412.6 MiB/s 2420.3 MiB/s ``` ## Step 2 - Identifying the target device List block devices with their file systems to find the empty disk: ```bash lsblk -f ``` ```output NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS vda ├─vda1 ext4 1.0 2b1c6a0e-9f0b-4f8e-8d1f-6c1b8f2a7e11 31.2G 18% / ├─vda14 └─vda15 vfat FAT32 6A2B-91C4 98.3M 6% /boot/efi vdb ``` `vdb` has no file system and no mount point, so it is the new volume. If it shows a file system you do not recognize, stop and find out what it contains before continuing. ## Step 3 - Creating the LUKS2 volume Initialize the device as a LUKS2 container. Ubuntu 24.04 defaults to LUKS2 with the `aes-xts-plain64` cipher, a 512-bit key (AES-256 in XTS mode) and the memory-hard `argon2id` key derivation function, so no extra options are needed: ```bash sudo cryptsetup luksFormat /dev/vdb ``` Type `YES` in capital letters to confirm, then enter a strong passphrase twice: ```output WARNING! ======== This will overwrite data on /dev/vdb irrevocably. Are you sure? (Type 'yes' in capital letters): YES Enter passphrase for /dev/vdb: Verify passphrase: ``` This passphrase is your recovery key. Store it in a password manager; you will use a key file for everyday unlocking in Step 5. Inspect the new header to confirm the settings: ```bash sudo cryptsetup luksDump /dev/vdb | grep -E "Version|cipher|PBKDF" ``` ```output Version: 2 cipher: aes-xts-plain64 PBKDF: argon2id ``` ## Step 4 - Opening the volume and creating a file system Unlock the container. This creates a decrypted device at `/dev/mapper/securedata`; everything written to it is encrypted before it reaches `/dev/vdb`: ```bash sudo cryptsetup open /dev/vdb securedata ``` Enter the passphrase when prompted. Create an ext4 file system on the decrypted device and mount it: ```bash sudo mkfs.ext4 -L securedata /dev/mapper/securedata sudo mkdir -p /srv/secure sudo mount /dev/mapper/securedata /srv/secure ``` Verify the device stack. The `crypt` layer sits between the disk and the mounted file system: ```bash lsblk -o NAME,TYPE,FSTYPE,SIZE,MOUNTPOINTS /dev/vdb ``` ```output NAME TYPE FSTYPE SIZE MOUNTPOINTS vdb disk crypto_LUKS 50G └─securedata crypt ext4 50G /srv/secure ``` You can also check the mapping status (output trimmed): ```bash sudo cryptsetup status securedata ``` ```output /dev/mapper/securedata is active and is in use. type: LUKS2 cipher: aes-xts-plain64 keysize: 512 bits device: /dev/vdb mode: read/write ``` ## Step 5 - Unlocking the volume automatically at boot A remote server usually has nobody available to type a passphrase at boot. The common solution is a key file stored on the root disk and referenced in `/etc/crypttab`. Be clear about what this protects. With the key file on the (unencrypted) root disk, anyone who obtains both disks, or root access to the running server, can read the data. It still protects the data volume if it is detached, reused or snapshotted on its own, which is the main risk for a separate volume. If you need protection against someone with the whole server image, keep the volume locked at boot and unlock it manually or from a key server after each reboot. Create a directory for keys, readable only by root, and generate a random 4096-bit key file: ```bash sudo install -d -m 0700 /etc/cryptsetup-keys.d sudo dd if=/dev/urandom of=/etc/cryptsetup-keys.d/securedata.key bs=512 count=1 sudo chmod 0400 /etc/cryptsetup-keys.d/securedata.key ``` Add the key file to a new key slot. You are asked for the existing passphrase to authorize the change: ```bash sudo cryptsetup luksAddKey /dev/vdb /etc/cryptsetup-keys.d/securedata.key ``` Confirm that two key slots are now in use (slot 0 is the passphrase, slot 1 the key file): ```bash sudo cryptsetup luksDump /dev/vdb | grep -E "^\s+[0-9]+: luks2" ``` ```output 0: luks2 1: luks2 ``` Find the UUID of the LUKS device, which is stable across reboots unlike `/dev/vdb`: ```bash sudo blkid -s UUID -o value /dev/vdb ``` ```output 8f3d2a61-5c47-4b0e-9e21-7d6a4c1f9b30 ``` Open the crypttab file: ```bash sudo nano /etc/crypttab ``` Add a line with the mapping name, the device UUID, the key file and the options. Replace the UUID with your own: ```text securedata UUID=8f3d2a61-5c47-4b0e-9e21-7d6a4c1f9b30 /etc/cryptsetup-keys.d/securedata.key luks ``` Then open the file system table: ```bash sudo nano /etc/fstab ``` Add a line to mount the decrypted device. `nofail` lets the server finish booting even if the volume cannot be unlocked, so you can still log in and fix it: ```text /dev/mapper/securedata /srv/secure ext4 defaults,nofail 0 2 ``` Test the whole chain without rebooting. Unmount and close the volume, reload systemd so it reads the new files, and start the generated unlock unit: ```bash sudo umount /srv/secure sudo cryptsetup close securedata sudo systemctl daemon-reload sudo systemctl start systemd-cryptsetup@securedata.service sudo mount -a findmnt /srv/secure ``` ```output TARGET SOURCE FSTYPE OPTIONS /srv/secure /dev/mapper/securedata ext4 rw,relatime ``` The volume was unlocked with the key file, without a passphrase prompt. Reboot once during a maintenance window and run `findmnt /srv/secure` again to confirm that it also works at boot. ## Step 6 - Moving a MySQL data directory onto the encrypted volume With the volume in place, put your sensitive data on it. A bind mount keeps the original path, so MySQL's configuration and Ubuntu's AppArmor profile keep working unchanged. The same approach works for PostgreSQL (`/var/lib/postgresql`) or an application's upload directory. Stop MySQL and copy its data directory, preserving ownership and permissions: ```bash sudo systemctl stop mysql sudo rsync -a /var/lib/mysql/ /srv/secure/mysql/ ``` Move the original directory aside and create an empty mount point: ```bash sudo mv /var/lib/mysql /var/lib/mysql.old sudo mkdir /var/lib/mysql ``` Open `/etc/fstab` again: ```bash sudo nano /etc/fstab ``` Add the bind mount below the line you added in Step 5. The `x-systemd.requires-mounts-for` option makes sure it is mounted only after `/srv/secure`: ```text /srv/secure/mysql /var/lib/mysql none bind,nofail,x-systemd.requires-mounts-for=/srv/secure 0 0 ``` MySQL must never start on an empty directory if the volume is missing, because it would create a new, empty database. Make the service depend on the mount with a drop-in: ```bash sudo systemctl edit mysql ``` ```ini [Unit] RequiresMountsFor=/var/lib/mysql ``` Mount the directory and start MySQL: ```bash sudo systemctl daemon-reload sudo mount /var/lib/mysql sudo systemctl start mysql ``` Verify that the data directory is on the encrypted device and that your databases are there: ```bash findmnt /var/lib/mysql sudo mysql -e "SHOW DATABASES;" ``` ```output TARGET SOURCE FSTYPE OPTIONS /var/lib/mysql /dev/mapper/securedata[/mysql] ext4 rw,relatime ``` Once you have confirmed that the application works, delete the old copy: ```bash sudo rm -rf /var/lib/mysql.old ``` > Note: Deleting files does not reliably erase them from the underlying storage, especially on SSDs. Blocks from the old, unencrypted data directory may remain on the root disk. For data that must never exist unencrypted, create the encrypted volume before loading the data, or restore it from an encrypted dump directly onto the volume. ## Step 7 - Backing up the LUKS header and managing keys The LUKS header at the start of the device holds the encrypted master key. If it is damaged, the data is unrecoverable, even with the right passphrase. Back it up: ```bash sudo cryptsetup luksHeaderBackup /dev/vdb --header-backup-file /root/securedata-luks-header.img sudo chmod 0400 /root/securedata-luks-header.img ``` Copy this file off the server to a secure location, together with the passphrase from Step 3, and delete the local copy. Keep in mind that the header backup plus any valid passphrase or key file decrypts the volume, even after you later remove that passphrase from the live header. Protect it like the data itself. If the header is ever corrupted, restore it with: ```bash sudo cryptsetup luksHeaderRestore /dev/vdb --header-backup-file /path/to/securedata-luks-header.img ``` To rotate a passphrase, for example when an administrator leaves, add the new one and then remove the old one. `luksRemoveKey` asks for the passphrase to delete: ```bash sudo cryptsetup luksAddKey /dev/vdb sudo cryptsetup luksRemoveKey /dev/vdb ``` This changes who can unlock the volume but not the master key that encrypts the data. To replace the master key itself, LUKS2 can re-encrypt the device online with `sudo cryptsetup reencrypt /dev/vdb`. It rewrites every block, so take a backup first, run it during a quiet period, and create a new header backup afterwards. ## Step 8 - Encrypting backups and exported files Files copied off the encrypted volume, such as database dumps, are plain text again. Encrypt them before they leave the server. For an occasional file, symmetric encryption with GnuPG is enough: ```bash gpg --symmetric --cipher-algo AES256 your_database.sql ``` GnuPG asks for a passphrase and writes `your_database.sql.gpg`. Delete the unencrypted file after checking that the encrypted one decrypts: ```bash gpg --decrypt your_database.sql.gpg | head -n 3 rm your_database.sql ``` For automated backups, use public key encryption so the server can encrypt but never decrypt, or a backup tool that encrypts by default, such as restic or BorgBackup. ## Troubleshooting **`systemd-cryptsetup@securedata.service` fails at boot.** Check the error with `journalctl -b -u systemd-cryptsetup@securedata.service`. The usual causes are a wrong UUID in `/etc/crypttab` (compare with `sudo blkid /dev/vdb`) or a key file path typo. Because of `nofail`, the server still boots and you can log in to fix it. **`No key available with this passphrase`.** The passphrase or key file does not match any key slot. Try the recovery passphrase from Step 3. If none work and the header was overwritten, restore it from the backup made in Step 7. **MySQL does not start after a reboot.** Run `findmnt /var/lib/mysql`. If nothing is mounted, the encrypted volume did not unlock; fix that first, then run `sudo mount -a` and `sudo systemctl start mysql`. The `RequiresMountsFor` drop-in is what prevented MySQL from starting on an empty directory. ## Conclusion You encrypted a data volume with LUKS2, configured automatic unlocking with a key file through `crypttab`, moved a MySQL data directory onto it with a bind mount, and backed up the LUKS header and passphrase needed for recovery. Your stored data is now protected against lost disks and leaked snapshots. As next steps, encrypt your offsite backups with public key encryption, restrict and audit who can log in to the unlocked server, and document your key management procedure for compliance frameworks such as GDPR or PCI DSS.