My workstation has a healthy amount of RAM, and a 120 GB LUKS2-encrypted Swap partition sitting on an NVMe RAID volume. So when the system started crawling under memory pressure: multi-second mouse lag, hanging windows, constant spinners, firefox just sitting like roadkill... it was clear that something had gone horribly wrong. The kernel was constantly struggling with memory even though a huge Swap partition was sitting right there.
The cause?: The LUKS2 container holding the Swap partition was not being unlocked at boot. It wasn't a broken Swap partition. It was an orphaned one — present on disk, correctly formatted but invisible to the system at runtime.
Follow this guide, step by step. These steps can be generally applied to all systemd-based distribution (Fedora, Ubuntu, Arch, openSUSE, etc.), regardless of whether your Swap lives on a plain partition, LVM, or RAID.
NOTE: UUIDs, device names, hostnames, and paths below are anonymised placeholders. You'll need to substitute real IDs as you follow along.
1) Get your block device layout.
lsblk -o NAME,TYPE,FSTYPE,SIZE,MOUNTPOINTSWhen functioning correctly (and LUKS2-contained partitions are loaded) a LUKS2 container will have a crypt child under the crypto_LUKS layer. For example, a partition of type crypto_LUKS will have a luks-<uuid> child holding a filesystem or Swap:
NAME TYPE FSTYPE SIZE MOUNTPOINTS
zram0 disk swap 8G [SWAP]
nvme0n1 disk 1.8T
`-nvme0n1p1 part linux_raid_member 1.8T
`-md0 raid5 LVM2_member 3.6T
|-vg-example-lv-root lvm crypto_LUKS 1.8T
| `-luks-<ROOT-UUID> crypt btrfs 1.8T /
|-vg-example-lv-swap lvm crypto_LUKS 120G
`-vg-example-lv-scratch lvm crypto_LUKS 1.7T
`-luks-<SCRATCH-UUID> crypt xfs 1.7T /scratchPay attention! The root (vg-example-lv-root) and scratch (vg-example-lv-scratch) volumes each have an unlocked crypt child beneath them. The Swap volume, vg-example-lv-swap with type crypto_LUKS has no such child. This indicates that it's a LUKS container that's not "opened" and whatever partitions are in it, were not mounted.
Also take note: the only active Swap on whole machine was zram0 (compressed RAM Swap). Everything else was locked up.
2) Confirm what Swap the kernel is using.
swapon --showIf you've only got system-default Swap, this is what you'd see. This was the cmd/output that confirmed that the Swap partition I'd carefully set aside and encrypted as a matter of security/privacy best practices was not being utilised as it should be:
NAME TYPE SIZE USED PRIO
/dev/zram0 partition 8G 0B 100One Swap device, 8 GB of zram, nothing else. If what you're doing managed to fit in your RAM+zram, you won't notice the missing disk Swap. On the other hand!, if you've got a billion tabs open in Firefox?, you're gonna start experiencing real pungent pain!!!
3) Check you boot configuration.
Two files control whether an encrypted device is mounted on boot:
/etc/crypttab: tells systemd to unlock the LUKS container at boot/etc/fstab: tells the system which devices to mount and wear and, in the case of a Swap partition, informs the system to runswaponon the unlocked Swap device
cat /etc/crypttab; echo ---; grep -i swap /etc/fstabOn my machine, /etc/crypttab listed entries for the root, /home, and /scratch volumes — but nothing for the Swap volume. And grep -i swap /etc/fstab returned nothing.
So the full picture of the failure:
- No
crypttabentry → the container is never unlocked at boot - No
fstabSwap entry → even if it were unlocked, nothing would activate it
I knew from memory Swap was set up correctly inside a LUKS container on a dedicated RAID partition. I guess that, when it had been created via mkswap during Fedora installation, the wiring hadn't been completed properly for it to be CRYPTTAB-opened and FSTAB-mounted at boot, which is why I refer to it as an "orphan encrypted partition" in this article.
4) ID the LUKS UUID of the Swap volume.
Next, you'll need the container's UUID for the crypttab entry:
sudo blkid /dev/mapper/vg-example-lv-swapOutput:
/dev/mapper/vg-example-lv-swap: UUID="<LUKS-UUID>" TYPE="crypto_LUKS"Note that this is the UUID of the LUKS container, NOT of the Swap filesystem inside it. crypttab wants this one.
5) Unlock manually and verify what's inside.
Before changing any configuration, it's important to confirm that the container actually holds a Swap partition:
# Unlock the LUKS container partition - you will be asked for the container's passphrase.
sudo cryptsetup luksOpen /dev/mapper/vg-example-lv-swap luks-swap-test\
# Run `blkid` again to view the partitions that exist within the LUKS container.
sudo blkid /dev/mapper/luks-swap-testExpected output:
/dev/mapper/luks-swap-test: UUID="<SWAP-UUID>" TYPE="swap"In the output, the TYPE="swap" confirms that there's indeed a Swap filesystem in the container... Also confirming that the issue with it not being used is with the boot-time configuration. Let's close the container and move on:
# Close the test mapper so the container is locked again before proceeding.
sudo cryptsetup close luks-swap-test6) Create a keyfile and enroll it as a new LUKS keyslot in order to automate decryption for mounting at boot.
I've heard tell that, given multiple LUKS containers in Fedora (the dist I use) when you're installing it, usually, as long as all the LUKS containers/encrypted partitions share the same passphrase, Fedora setup (Anaconda web installer nowadays) should wire things up so that all LUKS containers are automatically "opened" and their partitions mounted. Clearly something went wrong when I was installing Fedora on my machine... I used Mistral AI to debug things quickly (see Credits and Disclaimer section at end of article for further info) and, it suggested that the standard solution for a LUKS2 Swap was to create a keyfile for the system to use to "open"/decrypt the container and make the contained-partitions available for mount at boot. A keyfile is a small file of random bytes that can be "enrolled" to be an additional LUKS keyslot - then systemd can be informed about it so that it can unlock the container automatically during boot activities.
According to Mistral, this is a secure and safe method to use as long as the keyfile exists on an encrypted partition that is automatically decrypted/mounted at boot. Since my Root filesystem is itself LUKS-encrypted and is being loaded correctly at boot time, the keyfile created in /etc/cryptsetup-keys.d is only readable after the Root partition is successfully unlocked by the user and thus, it's relatively safe for it to exist. If your Root partition is not encrypted, you'll need to reconsider... in this case, a more complex TPM based enrollment (apparently using systemd-cryptenroll) is the better option.
Steps to create the Keyfile
# Create the standard directory for cryptsetup keyfiles without erroring if it already exists.
sudo mkdir -p /etc/cryptsetup-keys.d
# Generate 512 random bytes of key material and write it to the keyfile.
sudo dd if=/dev/urandom of=/etc/cryptsetup-keys.d/swap.key bs=512 count=1 status=none
# Restrict the keyfile to Root-only access so no other user can read the key.
sudo chmod 600 /etc/cryptsetup-keys.d/swap.key
# Enroll the keyfile as a new LUKS `keyslot`, prompting for the existing passphrase to authorize it.
sudo cryptsetup luksAddKey /dev/mapper/vg-example-lv-swap /etc/cryptsetup-keys.d/swap.keyThe luksAddKey step prompts for your existing passphrase to authorise the change; there should be no output message and, a 0 status indicates success.
After this, my LUKS2 header had two keyslots: slot 0 for my passphrase and slot 1 for the keyfile. Either can be used to unlock the volume.
7) Confirm that the keyfile can unlock the container.
# Unlock the swap container using only the keyfile to prove it works without a passphrase.
# This will add the opened container as a mapped device.
sudo cryptsetup luksOpen \
/dev/mapper/vg-example-lv-swap luks-swap-test \
--key-file=/etc/cryptsetup-keys.d/swap.key
# Print the exit status; zero means the keyfile unlock succeeded. Output of this should be `0`.
echo $?
# If you use Fish Shell like me, you'd run the following instead, expecting the same `0` output.
echo $status
# Close the test mapper device to lock the container again.
sudo cryptsetup close luks-swap-testIf the status was indeed 0 and you weren't prompted for a passphrase, the keyfile works. NOTE!: it might take several seconds for the container to unlock, this is normal (according to Mistral).
8) Wire the new keyfile into crypttab and fstab in order to inform the system and automate decrypt/mount on boot.
# Append an entry to `/etc/crypttab`:
# `systemd` will be informed on how to unlock the LUKS container using the keyfile.
echo 'luks-swap UUID="<LUKS-UUID>" /etc/cryptsetup-keys.d/swap.key luks,nofail' | sudo tee -a /etc/crypttab
# Append an entry to `/etc/fstab`:
# the unlocked device will be mounted as a Swap partition during the boot sequence.
echo '/dev/mapper/luks-swap none swap sw,nofail 0 0' | sudo tee -a /etc/fstabOne thing to note: if you have zram Swap setup (Fedora does this by default), the zram device holds the higher priority, and the disk Swap only fills once zram is exhausted. That's the recommended arrangement (according to Mistral): fast compressed RAM Swap first and only when it's full use the encrypted disk Swap as overflow. This is the default priority ordering - zram's priority is pre-set to be the preferred Swap used.
9) Take note: systemd "units" that are generated by the system escape special characters...
To test the new configuration out without having to cycle through a reboot, you'd run:
sudo systemctl daemon-reload
sudo systemctl start systemd-cryptsetup@luks-swap.serviceBut take note! The result at the time I tried this was:
Failed to start systemd-cryptsetup@luks-swap.service: Unit systemd-cryptsetup@luks-swap.service not found.The reason was that systemd-cryptsetup@... units generated based upon entries in /etc/crypttab are generated by systemd-cryptsetup-generator and generation happens when systemd loads/reloads its configuration. Editing crypttab at runtime will do nothing until you run:
sudo systemctl daemon-reloadAfter daemon-reload, if we inspect what the generator actually emitted running:
ls /run/systemd/generator/ | grep -i cryptExpected Output:
cryptsetup.target.requires
cryptsetup.target.wants
dev-mapper-luks\x2dswap.device.requires
dev-mapper-luks\x2dswap.swap
systemd-cryptsetup@luks\x2dswap.service
...You'll see that systemd escapes hyphens - - - by using the \x2d character in unit instance names. So, since we named our container luks-swap in /etc/crypttab, he unit generated isn't systemd-cryptsetup@luks-swap.service, it's named: systemd-cryptsetup@luks\x2dswap.service.
It's also important to note that the same escaping explains why the generated .swap unit for the fstab entry is named dev-mapper-luks\x2dswap.swap.
THUS... you'll start the generated SystemD Swap service unit by using the file name you saw in the output from ls /run/systemd/generator/ | grep -i crypt... and, in the command below, if all went well, running swapon will show you that your encrypted swap partition is now up and available.
# Start the generated swap unit:
# it pulls runs the cryptsetup unlock and activates the Swap
# in the same way that is performed during the boot sequence.
sudo systemctl start 'dev-mapper-luks\x2dswap.swap'
# This should confirm that both swap devices: zram and your encrypted Swap
# are now active and show their sizes and priorities.
swapon --showIn my case, the output of swapon --show is:
NAME TYPE SIZE USED PRIO
/dev/zram0 partition 8G 0B 100
/dev/dm-6 partition 119.2G 0B -1Both Swap devices are visibly available and active with the zram at priority 100 (first to be used by the system) and, the encrypted disk Swap at priority -1, making it the fallback.
12) Reboot and verify automatic unlock and mount.
sudo systemctl rebootThen, after login, run:
# Confirm both Swap devices are still visible and active.
swapon --showSecurity and housekeeping notes
- Keyfile at rest:
/etc/cryptsetup-keys.d/swap.keyis only safe because it exists on an encrypted partition. You MUST NOT back it up to unencrypted media!, and DO NOT COPY IT to any machine you don't control - this file controls the decryption of the Swap partition and, if a malicious actor got their hands on it AND they managed to grap the Swap partition image with data on it, they'd be able to decrypt that data. - The zram ordering is intentional: Disk Swap should overflow to zram FIRST, this is the recommended configuration; resist the urge to raise its priority unless you know what you're doing.
Credits and Disclaimer
I used Mistral AI to help me debug this. If you don't like using LLMs (and I don't but I do from time to time) and you can wait to interact with other humans, then, the traditional way to debug an issue like this would have been to approach folks at a tech forum, like ServerFault and, over a few days, you'd most likely learn how to fix the issue AND, retain more of what you did than I did... I point this out because, other than being sure I'll recognise these symptoms in the future, I'm pretty sure there's no way I'll remember how to conduct these debug/configuration fix steps... Which, btw, is the reason I decided to take the time to document all this stuff in a blog post!
I also got Mistral provide me the raw material for this blog post which I then scrubbed through word-for-word, line-by-line (goodness, LLMs so painfully and uncessarily verbose!), in order to make it all make sense. Why? am I sharing this stuff about LLM usage?... Because I'm NOT an expert sysadmin so, if you get in touch with me thinking I can help you debug this from scratch, sorry folks, that's not gonna happen!... I didn't even know about the swapon cmd until Mistral used it in one of these commands...
You live, you learn!
I hope this article helps you and hope you'll share this info in your socials!