Contents

LUKS Full-Disk Encryption With a Remote Unlock

Encrypting a machine you cannot walk over to

Contents

Full-disk encryption on a laptop is a solved problem that everyone agrees on. Full-disk encryption on a headless server is where the agreement collapses, because encryption requires a passphrase at boot, and a headless server boots at 04:12 after a power cut while you are asleep in another building.

The three ways people resolve this are: skip encryption entirely and hope; store the key on the machine, which is a lock with the key taped to it; or arrange for the passphrase to arrive from somewhere else at boot time. The third one is the interesting one, and it splits into two genuinely different designs with different threat models.

I want to be upfront about what this protects against, because the field is full of people encrypting servers for reasons they have not examined. Disk encryption defends against physical possession of the drive. A stolen machine, a decommissioned SSD that goes in a bin, a drive returned under warranty with your data on it, a burglary. Against a running machine that an attacker has root on, it does precisely nothing, because the disk is unlocked and the key is in RAM. If your worry is remote compromise, this is the wrong control and you should be reading about SSH hardening instead.

For a homelab the honest case is narrow and real: you will eventually throw these drives away, and a decommissioned drive that was encrypted from day one needs no wiping ritual and no anxiety.

Getting LUKS2 right first

Advertisement

Most guides date from LUKS1 and quietly leave money on the table. Check what you have:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ cryptsetup luksDump /dev/nvme0n1p3 | head -20
LUKS header information
Version:        2
Epoch:          6
Metadata area:  16384 [bytes]
Keyslots area:  16744448 [bytes]
UUID:           8f4c1a2e-...
Label:          (no label)
Subsystem:      (no subsystem)
Flags:          (no flags)

Data segments:
  0: crypt
        offset: 16777216 [bytes]
        length: (whole device)
        cipher: aes-xts-plain64
        sector: 4096 [bytes]

Two things matter here. Version: 2 gives you Argon2id key derivation, which is memory-hard and therefore expensive to attack with a GPU farm — LUKS1’s PBKDF2 is not, and a rented GPU chews through it. And sector: 4096 matches modern SSDs; the 512-byte default costs real throughput on NVMe.

Creating a new volume with sensible parameters:

1
2
3
4
5
6
7
8
9
cryptsetup luksFormat --type luks2 \
  --cipher aes-xts-plain64 \
  --key-size 512 \
  --pbkdf argon2id \
  --pbkdf-memory 1048576 \
  --pbkdf-parallel 4 \
  --iter-time 5000 \
  --sector-size 4096 \
  /dev/nvme0n1p3

--key-size 512 is XTS mode splitting the key in two, so that is AES-256 rather than AES-512. Everyone misreads this once.

--pbkdf-memory 1048576 asks Argon2id to use 1 GiB during key derivation. That is the knob that actually costs an attacker money, and the constraint is that the machine must have that much RAM available in the initramfs, before most of the system exists. On a server with 32 GB, fine. On a Raspberry Pi, that setting produces a box that cannot unlock its own disk, which is a memorable afternoon.

Back the header up before you do anything else, and put the copy somewhere other than the encrypted disk:

1
2
cryptsetup luksHeaderBackup /dev/nvme0n1p3 \
  --header-backup-file luks-header-$(hostname).img

The header holds the keyslots. Corrupt those 16 MB and every byte on the drive is permanently, cryptographically gone, with a correct passphrase and no way to use it. This is the single most likely way to lose data to LUKS, and it costs one command to prevent. Treat that file as sensitive — it is attackable offline — and store it with your other secrets.

One more thing worth saying about keyslots, because it is where people quietly lose access. LUKS2 has 32 of them, each holding the volume key encrypted under a different passphrase or token. Every method above — your passphrase, Clevis’s binding, a TPM enrolment — is one keyslot. They are independent, and removing one does nothing to the others.

That independence is the property to exploit. Keep a long passphrase in slot 0, written down on paper and stored somewhere physical, and never type it in anger. Use Clevis or dropbear for the day-to-day. When you change the automation — new Tang server, rebuild, a policy change — the paper passphrase is the thing that stops a configuration error from becoming a data loss event. cryptsetup luksDump lists which slots are populated, and looking at that output and being unable to say what each slot is for is a bad sign you should resolve while the disk is still open.

If you want that paper passphrase to live on a hardware token instead, a FIDO2 key works the same way — systemd-cryptenroll --fido2-device=auto puts a keyslot behind a physical touch, which is a nice middle ground and pairs well with the rest of a YubiKey-centred setup.

Design one: SSH into the initramfs

The first approach puts a tiny SSH server in the initramfs. The machine boots, sets up networking, and waits. You SSH in, type the passphrase, and the boot continues.

1
2
3
4
5
6
7
apt install dropbear-initramfs

# Your unlock key goes here — a DIFFERENT key from your normal login key
cat ~/.ssh/id_ed25519_unlock.pub >> /etc/dropbear/initramfs/authorized_keys

# /etc/dropbear/initramfs/dropbear.conf
DROPBEAR_OPTIONS="-I 180 -j -k -p 2222 -s"

Then static networking in the initramfs, because DHCP at that stage is a coin flip:

1
2
# /etc/initramfs-tools/initramfs.conf
IP=192.168.1.50::192.168.1.1:255.255.255.0:gate01:eth0:off

And rebuild:

1
update-initramfs -u -k all

The unlock itself:

1
2
3
4
$ ssh -p 2222 [email protected]
Please unlock disk nvme0n1p3_crypt: ********
cryptsetup: nvme0n1p3_crypt set up successfully
Connection to 192.168.1.50 closed.

Port 2222 is deliberate. The initramfs dropbear has a different host key from the full system’s OpenSSH, so if both answer on port 22 your client sees the host key change on every unlock and refuses to connect — and you learn to ignore that warning, which is a habit worth not acquiring. Separate ports, separate known_hosts entries, no alarms to train yourself out of.

The -s disables password auth, -j -k disable forwarding, and -I 180 drops idle sessions.

The honest weakness: a passphrase you type is a passphrase you have to be awake for. Power cut at 04:12 means the machine sits at the prompt until morning. That is fine for a workstation. It is poor for anything you depend on.

Design two: Tang and Clevis, unlock by network presence

Advertisement

The second approach is the one I actually run, and it inverts the question. Rather than asking a human, the machine asks the network.

Tang is a server that performs a key exchange. Clevis is the client that binds a LUKS keyslot to it. The machine boots, contacts the Tang server, recovers the key material, unlocks, and continues — all in about two seconds and with nobody awake.

The clever part is that no key is ever stored anywhere. Tang holds no secrets belonging to your machine, and cannot decrypt anything on it. It participates in a McCallum-Relyea exchange that only succeeds when the client already holds the other half. Steal the Tang server and you gain nothing. Steal the encrypted machine and take it home and it cannot reach the Tang server, so it will not unlock.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# On a small always-on box — a Pi is ideal
apt install tang
systemctl enable --now tangd.socket

# On the machine to be encrypted
apt install clevis clevis-luks clevis-initramfs

clevis luks bind -d /dev/nvme0n1p3 tang '{"url":"http://192.168.1.9"}'
# It shows the key thumbprint and asks you to confirm — check it
update-initramfs -u -k all

That is the whole configuration. The threat model becomes: this disk unlocks itself when it is physically on my network, and it is a brick anywhere else. For a burglary or a bin, that is exactly the property you wanted.

The obvious follow-up: what if the Tang server is the machine that died? Bind to two of them with an SSS policy requiring one of two, and keep your passphrase in a keyslot as the fallback:

1
2
clevis luks bind -d /dev/nvme0n1p3 sss '{"t":1,"pins":{"tang":[
  {"url":"http://192.168.1.9"},{"url":"http://192.168.1.10"}]}}'

Tang has an important limitation worth stating plainly: it is HTTP, and it is designed to be. The exchange does not require confidentiality, so TLS adds nothing cryptographically — though it does mean anyone on your network can see which machines are unlocking, and that is a real if minor leak. Also, a Tang server on the same LAN as the encrypted machine means someone who steals the whole rack has stolen both halves. The answer is to put it somewhere the rack isn’t: a Tang server reached over a WireGuard tunnel the initramfs brings up is more moving parts and a much better story.

The third option: let the TPM do it

There is a design I have deliberately skipped so far, and it deserves a fair hearing because on modern hardware it is the default answer everywhere else.

A TPM is a small chip that will release a secret only when the machine’s boot measurements match what they were when the secret was sealed. Enrol a LUKS keyslot into it and the disk unlocks with no network, no passphrase and no Tang server. Windows has done this with BitLocker for over a decade, and systemd-cryptenroll makes it a one-liner on Linux:

1
systemd-cryptenroll /dev/nvme0n1p3   --tpm2-device=auto   --tpm2-pcrs=7+11

The PCR selection is the entire security argument. PCR 7 covers the Secure Boot state and PCR 11 covers the kernel and initrd as measured by systemd-stub. Seal against those and the TPM releases the key only when the machine boots the software you sealed against — which is a genuinely strong property, and it depends completely on Secure Boot being configured properly, because without it PCR 7 measures a policy that permits anything.

Why I do not run it in the homelab comes down to two things.

The first is that TPM unlock defends against a different threat than the one I have. It stops someone booting a modified kernel to extract your data. It does nothing whatsoever about someone walking off with the machine, because the machine unlocks itself wherever it is — the thief plugs it in at home and it obligingly decrypts. Adding a PIN restores that property and reintroduces the human at 04:12, which is the problem I started with.

The second is fragility. PCR 11 changes when the kernel changes, which is to say every few weeks. systemd-cryptenroll re-seals on update if the plumbing is right, and when the plumbing is not right the machine simply does not boot, and the failure arrives after an unattended update rather than at a moment you chose. A firmware update, a BIOS setting you toggled, a Secure Boot key rotation — each is a plausible route to a box that will not unlock.

The fair summary: TPM unlock is the right answer for a laptop, where the threat is a modified boot chain and where you are present to type a PIN. Tang is the right answer for a server that must come up alone, because “is this machine on my network” is a much closer match to what a homelabber actually fears than “is this machine running the kernel I signed”.

Troubleshooting

The initramfs has no network at all. Its module set is minimal and your NIC driver is missing. MODULES=most in /etc/initramfs-tools/initramfs.conf includes everything and costs you a larger initramfs, which nobody has ever noticed. Realtek 2.5GbE chips are the usual culprit.

Dropbear answers but the passphrase prompt never appears. You SSH’d in before cryptsetup started asking, or the FIFO is not there yet. Run cryptroot-unlock manually once connected; that is the script the login shell normally runs for you.

Clevis times out and drops to the passphrase prompt. Almost always DHCP. The initramfs asked for an address, nothing answered fast enough, and Clevis tried to resolve the Tang URL with no network. Static IP in the initramfs, and use an IP address rather than a hostname in the Tang URL — there is no DNS at that point either.

Unlock is correct but takes forty seconds. Argon2 memory cost meeting a machine that does not have the RAM free in the initramfs. cryptsetup luksConvertKey --pbkdf-memory 262144 lowers it. Check with cryptsetup benchmark before you pick a number.

update-initramfs succeeds, the change does not take effect. Two initramfs images, and the bootloader is pointing at the other one. -k all rather than -k $(uname -r), then confirm the bootloader entry.

After a kernel update the machine will not unlock. The hook did not run for the new kernel. This is the argument for testing a reboot in daylight after every kernel update on a remote box, and for keeping a way in that does not depend on the machine booting properly to anything you cannot walk to.

The verdict

Encrypt the NAS. Encrypt anything holding documents, photos, backups or credentials. Those drives are going in a bin eventually and encryption is what makes that a non-event rather than a shredding appointment.

Skip it on the machine that only runs stateless containers pulled from a registry. There is nothing on it worth an attacker’s time, and you have added a boot dependency to a machine that had none.

Between the two designs: dropbear if you have one encrypted box and you are usually around; Tang if you have several, or if unattended reboots matter. I ran dropbear for two years and switched, and the reason was banal — a power cut on a Saturday while I was out, and a house with no internet until I got back to type a passphrase.

The two things people skip and regret: back up the header, today, before the interesting configuration. And test the unlock path by actually rebooting the machine on purpose, while you are physically near it, before you rely on it. A remote unlock you have never rehearsed is a remote unlock you do not have — the same argument as rehearsing a bare-metal restore, and it fails the same way, at the same hour, for the same reason.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.