Skip to content
VPSDen
Privacy engineering10 min read958 words

Full-disk encryption on a remote VPS, with remote unlock

LUKS2 on a remote server with dropbear-initramfs unlock: the argon2id parameters that matter, and the limitations nobody mentions until you hit them.

Short answer

How do I encrypt a VPS disk so the hosting provider cannot read it?

Install with LUKS2 full-disk encryption using argon2id key derivation, then add dropbear-initramfs so the machine halts at boot and waits for you to SSH in on a pre-boot shell and supply the passphrase. The provider then holds ciphertext plus a LUKS2 header; the key exists only in your possession and in the running machine's memory. The real limitations are that the key is in RAM while the machine runs — so a hypervisor operator or a live compromise can reach it — and that an unattended reboot leaves the server offline until you unlock it. Use argon2id with at least 1 GiB of memory cost, keep a header backup somewhere else, and pair it with an out-of-band console so a broken initramfs does not lock you out.

Full-disk encryption on a remote server has an obvious problem: nobody is standing at the console to type the passphrase. The standard answer is dropbear-initramfs, a tiny SSH server embedded in the initial ramdisk that lets you supply the key over the network before the root filesystem is mounted.

This is well-trodden ground and it works. What follows is the version we deploy, and — more usefully — an honest account of what it does not do.

What this actually protects against

Be precise, because vendors are routinely not:

ThreatProtected?
Drive removed from the rack and imagedYes
Provider compelled to hand over the volumeYes — they hand over ciphertext
Decommissioned drive resold or improperly wipedYes
Snapshot taken while the machine is powered offYes
Snapshot taken while the machine is runningNo — the key is in memory
Hypervisor operator dumping guest memoryNo
Live root compromise of the guestNo
Network traffic interceptionNo — that is TLS's job

The pattern: FDE protects data at rest on hardware that is not currently running your workload. That is a narrower guarantee than "the provider cannot read my data," but it is the guarantee that covers seizure, which is the scenario that actually happens.

Partition layout

A minimal, well-understood layout:

/dev/vda1   512M   ext4    /boot        (unencrypted — kernel + initramfs)
/dev/vda2   rest   LUKS2   → cryptroot  (everything else)
              └── ext4 or btrfs mounted at /

/boot must be readable by the bootloader, so it is unencrypted. This is a real exposure: an attacker with physical access could modify the kernel or the initramfs to capture your passphrase — the classic "evil maid" attack. Secure Boot with your own keys mitigates it; on most VPS platforms that is not available, so treat /boot as trusted-but-verifiable and check its hashes if the machine was ever powered down outside your control.

Creating the container

cryptsetup luksFormat \
  --type luks2 \
  --cipher aes-xts-plain64 \
  --key-size 512 \
  --hash sha512 \
  --pbkdf argon2id \
  --pbkdf-memory 1048576 \
  --pbkdf-parallel 4 \
  --iter-time 5000 \
  /dev/vda2

The parameters that matter:

  • --pbkdf argon2id — memory-hard key derivation. This is the single most important flag. PBKDF2, the LUKS1 default, is cheap to attack with GPUs; argon2id is not, because it forces the attacker to allocate memory per guess.
  • --pbkdf-memory 1048576 — 1 GiB per derivation attempt, and the number you must size to the machine. The derivation runs inside the initramfs, so if the instance does not have the memory free, unlock fails and you are booting rescue media. On a 2 GB instance use 524288 (512 MiB); 1 GiB is the right value from 4 GB upwards. Our own provisioning picks the parameter from plan RAM for exactly this reason.
  • --key-size 512 — with XTS this yields AES-256, since XTS splits the key in half.

Generate the passphrase on your own machine with a real generator — diceware, or openssl rand -base64 32. Never let the provider generate it, and never transmit it over anything but the pre-boot SSH session itself.

Remote unlock with dropbear

apt install dropbear-initramfs cryptsetup-initramfs

# your public key, pre-boot only — use a key you do not use elsewhere
echo 'ssh-ed25519 AAAA...' > /etc/dropbear/initramfs/authorized_keys
chmod 600 /etc/dropbear/initramfs/authorized_keys

# distinct port so your client does not warn about a changed host key
echo 'DROPBEAR_OPTIONS="-p 2222 -s -j -k -I 180"' \
  >> /etc/dropbear/initramfs/dropbear.conf

# static network in the pre-boot environment
echo 'IP=192.0.2.42::192.0.2.1:255.255.255.0::eth0:off' \
  >> /etc/initramfs-tools/initramfs.conf

update-initramfs -u -k all

Then, after a reboot:

# 192.0.2.x below is an RFC 5737 documentation address — substitute your own
ssh -p 2222 [email protected]
# cryptroot-unlock
Please unlock disk cryptroot: ********

Boot continues and the real SSH daemon comes up on port 22 with a different host key. Pin both in your ~/.ssh/known_hosts so a substituted pre-boot environment is detectable.

The -I 180 flag idles the pre-boot dropbear out after three minutes, which limits how long that surface is exposed if you do not connect.

The LUKS2 header holds the key slots. If it is corrupted, your data is gone — the ciphertext is intact and permanently unreadable. Back it up before you put anything on the volume:

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

Store that file somewhere that is not the server, and treat it as sensitive: it contains the key slots, so anyone with the header and your passphrase can decrypt the volume.

Add a second key slot with an independently stored recovery passphrase. LUKS2 supports up to 32 key slots — it is LUKS1 that was limited to 8 — so there is no reason to design around a tight budget. Using exactly one slot is how people lose data.

Limitations you must accept

  • Unattended reboots do not come back. A kernel panic at 3 a.m. leaves the machine sitting at a passphrase prompt until you wake up. This is the intended behaviour and it is the main operational cost. Some people mitigate it with a network-bound key (clevis/tang) held in a second jurisdiction, which trades some of the security property for availability.
  • The key is in RAM while running. Anything that can read guest memory can extract it. See our RAM-only article for the case where that matters most.
  • /boot is unencrypted. Verify its hashes after any powered-down period you did not control.
  • A broken initramfs locks you out. Always keep out-of-band console access available so you can boot rescue media. Test the unlock path before you put data on the machine — reboot deliberately, at least twice.

On VPSDEN, zero-knowledge LUKS is configured this way by default on every disk-backed plan, at no charge. We never receive the passphrase, and the out-of-band console is included so a bad initramfs is recoverable.

Cite this page

VPSDEN, “Full-disk encryption on a remote VPS, with remote unlock”, vpsden.com/kb/luks-remote-unlock-vps, revised 2026-06-02. Published under CC BY 4.0 — reproduce it freely, with attribution.

Found an error? We amend the article and name the reader who reported it. Tell us.

Offshore VPS from €4.40/month. No KYC, no email, paid in Monero.

Nine jurisdictions, RAM-only options, disks we cannot read, live in about 48 seconds.

No email · No KYC · Pay in Monero · Deployed in under a minute