Full-disk encryption that still stops for a passphrase at every reboot is only half done. On a laptop that is fine. On a homelab node, a fleet box, or anything you expect to come back after power loss, interactive unlock becomes the outage.
systemd-cryptenroll is the operator tool for binding LUKS2 volumes to hardware factors systemd already understands at boot: TPM2 chips, FIDO2 tokens (hmac-secret), PKCS#11 smartcards, plus computer-generated recovery keys. Unlock metadata lives in the LUKS2 JSON token area. At boot, systemd-cryptsetup + /etc/crypttab consume it.
This guide is operational. Commands and semantics come from systemd-cryptenroll(1), crypttab(5), and systemd-cryptsetup@.service(8) (Arch man pages / upstream systemd docs, covering features through systemd 262). Examples assume a LUKS2 volume you already unlock with a passphrase today. Do not wipe that passphrase until recovery and hardware unlock are proven.
What this is (and is not)
| Tool | Job |
|---|---|
| systemd-cryptenroll | Enroll TPM2 / FIDO2 / PKCS#11 / passphrase / recovery into LUKS2 |
| crypttab + systemd-cryptsetup | Unlock at boot from those enrollments |
| Clevis + Tang | Network-Bound Disk Encryption (NBDE) — unlock when a Tang server is reachable |
| systemd-creds | Per-service secrets at activation, not disk unlock |
| sbctl / Secure Boot | Boot chain PE signing — complementary, not a LUKS unlocker |
| cryptsetup luksAddKey | Passphrase/keyfile slots only — no TPM2/FIDO2 token JSON |
cryptenroll supports LUKS2 only. Older LUKS1 volumes need a format upgrade first (cryptsetup convert --type luks2, after backups).
Supported enrollment kinds (from the man page):
- PKCS#11 tokens (RSA or EC key pair)
- FIDO2 tokens with the
hmac-secretextension - TPM2 security devices
- Regular passphrases
- Recovery keys (machine-generated, high entropy, QR-friendly)
Prerequisites
# Tooling
command -v systemd-cryptenroll
systemd-cryptenroll --version # features below note added-in versions
# You need an existing LUKS2 volume and a working unlock factor today
# (usually a passphrase). Root (or equivalent) required.
# Discover candidate LUKS devices (257+)
systemd-cryptenroll --list-devices
# TPM present?
ls /dev/tpmrm0 2>/dev/null || ls /dev/tpm0 2>/dev/null
systemd-cryptenroll --tpm2-device=list
# FIDO2 present?
systemd-cryptenroll --fido2-device=list
Keep a second session or live USB path open before you wipe passphrase slots. A bad wipe without recovery is a brick for the volume key.
Mental model
Enrollment does not store your LUKS master key in the clear on the token.
- TPM2: a random unlock key is sealed to the TPM (primary key derived from the TPM seed / SRK). Sealed blob + policy metadata land in the LUKS2 JSON token header. Unseal succeeds only when the TPM can reproduce the policy (PCRs, optional PIN, signed PCR policy, pcrlock, …).
- FIDO2: a random salt/secret is HMAC'd on the token with a secret that never leaves the authenticator; the HMAC output unlocks the volume. Presence (tap), client PIN, and user verification are configurable.
- Recovery key: like a passphrase, but generated for entropy and typeability; intended as the “token lost / TPM state drifted” escape hatch.
Default device when you omit DEVICE and are not wiping: the block device backing /var/ (which is usually the root FS device when /var is not separate). Prefer naming the device explicitly.
Lab 0 — Safety first: recovery key
Before any TPM or FIDO experiment, enroll a recovery key and store it offline (password manager, printed QR, sealed envelope — not next to the machine).
# Replace with your LUKS partition (by path or /dev/disk/by-uuid/…)
DEV=/dev/disk/by-uuid/YOUR-LUKS-UUID
# Prompts for an existing passphrase to authorize the new slot
sudo systemd-cryptenroll --recovery-key "$DEV"
systemd-cryptenroll prints the recovery key and can show a QR code. Treat it like a root password with higher entropy.
List what is enrolled (via cryptsetup token listing):
sudo cryptsetup luksDump "$DEV" | sed -n '/Tokens/,/Keyslots/p'
# or full dump:
sudo cryptsetup luksDump "$DEV"
Lab 1 — TPM2 automatic unlock (no PCR bind)
Simplest useful path for a trusted physical host: seal to the TPM with no PCR policy. The volume unlocks whenever this TPM is present. Stolen disk alone is not enough; stolen disk plus the same machine’s TPM (or a cloned TPM state, which you should assume is hard on discrete/fTPM designs) is the threat you are accepting.
# List TPM devices
systemd-cryptenroll --tpm2-device=list
# Enroll; unlocks with your existing passphrase unless you pass --unlock-*
sudo systemd-cryptenroll --tpm2-device=auto "$DEV"
# Empty string / omitted --tpm2-pcrs= means: bind to no PCRs (man page default)
Wire crypttab so boot uses the TPM:
# /etc/crypttab — third field "none" when token metadata is in the LUKS header
# volume-name encrypted-device key-file options
rootfs UUID=YOUR-LUKS-UUID none tpm2-device=auto,x-initrd.attach
Notes from crypttab(5):
-
tpm2-device=autodiscovers the chip; or pin/dev/tpmrm0 -
x-initrd.attachis recommended for root (and other initrd-attached crypt devices) so detach ordering during shutdown is correct -
headless=truerefuses interactive password fallback — only enable after TPM path is proven -
token-timeout=(default 30s) waits for tokens before falling back to password
Rebuild initramfs so early unlock sees the TPM stack (distro-specific):
# Debian/Ubuntu
sudo update-initramfs -u
# Fedora
sudo dracut -f
# Arch
sudo mkinitcpio -P
Test without rebooting when the volume is not your live root (extra data disk):
sudo systemctl start systemd-cryptsetup@datadisk.service
# or:
sudo /usr/lib/systemd/systemd-cryptsetup attach datadisk "$DEV" - tpm2-device=auto
For root, plan a controlled reboot and keep recovery key + passphrase slots until you have one clean boot.
Lab 2 — Bind to Secure Boot / UKI-friendly PCRs
PCR binding refuses unseal if measured boot state drifts. The man page’s practical guidance:
In general, encrypted volumes would be bound to some combination of PCRs 7, 11, and 14 (if shim/MOK is used). Prefer certificate-backed measurements over raw firmware code PCRs (0/2), which change on every firmware update.
| PCR | Name (systemd) | Why it matters |
|---|---|---|
| 7 | secure-boot-policy | Secure Boot on/off and db/KEK/PK material |
| 11 | kernel-boot | systemd-stub UKI measurements; pcrphase milestones |
| 14 | shim-policy | MOK certificates/hashes when shim is in the path |
| 4 | boot-loader-code | Boot loader PE — changes on bootloader updates |
| 9 | kernel-initrd | Kernel-measured initrds (Linux 5.17+ LOAD_FILE2 path) |
# Example: Secure Boot policy + UKI/kernel-boot measurements
sudo systemd-cryptenroll \
--tpm2-device=auto \
--tpm2-pcrs=7+11 \
"$DEV"
# Named form also works:
# --tpm2-pcrs=secure-boot-policy+kernel-boot
Operational cost: kernel/UKI/Secure Boot changes that alter those PCRs break unlock until you re-enroll or fall back to recovery/passphrase. That is the point — and why a recovery key is mandatory.
Signed PCR policies (update-friendly)
Instead of sealing to today’s PCR digests, bind to a public key that signs allowed PCR states (--tpm2-public-key=, --tpm2-public-key-pcrs=, optional --tpm2-signature=). Vendors or your own UKI pipeline (see systemd-measure) can ship new signatures when kernels change without re-sealing every disk. Default public-key PCR set is 11 when unset. systemd 252+.
# Sketch — paths follow man-page search order if omitted:
# /etc/systemd/tpm2-pcr-public-key.pem and tpm2-pcr-signature.json
sudo systemd-cryptenroll \
--tpm2-device=auto \
--tpm2-public-key=/etc/systemd/tpm2-pcr-public-key.pem \
--tpm2-public-key-pcrs=11 \
--tpm2-signature=/etc/systemd/tpm2-pcr-signature.json \
"$DEV"
If a signature file is provided, cryptenroll verifies it unlocks the current PCR state before writing the slot — a safety net against locking yourself out mid-enrollment.
systemd 262 adds --tpm2-public-key-policyref= so one signing key can scope slots to a boot phase (for example initrd-only) when systemd-measure used a matching --policyref=.
Lab 3 — TPM2 + PIN (second factor)
A bare TPM unseal is “something you have” (the machine). Add a PIN so a cold-booted chassis still needs knowledge:
# systemd 262+: --tpm2-with-pin=yes hardens the PIN with Argon2id before the TPM
# (compromised TPM alone should not yield the volume key without the PIN)
sudo systemd-cryptenroll \
--tpm2-device=auto \
--tpm2-pcrs=7+11 \
--tpm2-with-pin=yes \
"$DEV"
# Older compatibility path: --tpm2-with-pin=direct (PBKDF2, systemd 253+ unlockers)
crypttab:
rootfs UUID=… none tpm2-device=auto,tpm2-pin=yes,x-initrd.attach
Dictionary lockout: wrong PINs hit the TPM’s global DA lockout. systemd does not own that policy — use tpm2_getcap / tpm2_dictionarylockout from tpm2-tools if you need to inspect or tune it. Keep recovery keys offline.
Optional Argon2id knobs (262+): --tpm2-argon2id-memory=, --tpm2-argon2id-iterations=, --tpm2-argon2id-parallelism=, --tpm2-argon2id-iter-time= (default target ~2s).
Lab 4 — FIDO2 token (YubiKey and friends)
Prefer FIDO2 over PKCS#11 on tokens that speak both — simpler enrollment, modern defaults.
systemd-cryptenroll --fido2-device=list
# Defaults: clientPin yes, user presence (tap) yes, user verification no
sudo systemd-cryptenroll --fido2-device=auto "$DEV"
# Explicit policy example:
sudo systemd-cryptenroll \
--fido2-device=auto \
--fido2-with-client-pin=yes \
--fido2-with-user-presence=yes \
--fido2-with-user-verification=no \
"$DEV"
crypttab:
rootfs UUID=… none fido2-device=auto,x-initrd.attach
Credential algorithm default is es256; rs256 and eddsa are available via --fido2-credential-algorithm= (251+). systemd 257 adds --fido2-salt-file= and --fido2-parameters-in-header= (header storage is the default and what auto unlock expects).
Multi-token caveat: several FIDO2 enrollments work, but tokens with user verification may force sequential PIN/UV prompts because pre-flight identity is limited. PKCS#11 does not have that limitation.
Lab 5 — Rotate and wipe without bricking
--wipe-slot= accepts numeric indexes or type names: password, recovery, tpm2, fido2, pkcs11, empty, all (with safety refusal of wiping everything with no remaining unlock path in some combinations — still: never wipe recovery until the new factor boots cleanly).
Re-enroll TPM and drop old TPM slots in one shot:
sudo systemd-cryptenroll \
--wipe-slot=tpm2 \
--tpm2-device=auto \
--tpm2-pcrs=7+11 \
--unlock-tpm2-device=auto \
"$DEV"
Enrollment runs first; the new slot is excluded from the wipe. Other patterns from the man page:
# Empty password → TPM (image factory pattern)
sudo systemd-cryptenroll --wipe-slot=empty --tpm2-device=auto "$DEV"
# PKCS#11 → FIDO2
sudo systemd-cryptenroll --wipe-slot=pkcs11 --fido2-device=auto "$DEV"
Unlock helpers when stdin passphrase is inconvenient (versions in man page):
| Flag | Use |
|---|---|
--unlock-key-file=PATH |
Key file instead of typed passphrase (252+) |
--unlock-fido2-device=auto |
Unlock with existing FIDO2 to enroll something else (253+) |
--unlock-tpm2-device=auto |
Unlock with existing TPM2 (256+) |
--unlock-empty |
Empty passphrase volumes (262+) |
--unlock-headless |
Try TPM2 then empty — provisioning automation (262+) |
Lab 6 — Loop-device dry run (no production disk)
When you want to practice without touching root:
set -euo pipefail
IMG=/tmp/cryptenroll-lab.img
MNT=/tmp/cryptenroll-mnt
sudo truncate -s 256M "$IMG"
LOOP=$(sudo losetup --find --show "$IMG")
sudo cryptsetup luksFormat --type luks2 "$LOOP"
sudo cryptsetup open "$LOOP" cryptenroll-lab
sudo mkfs.ext4 /dev/mapper/cryptenroll-lab
sudo mkdir -p "$MNT"
sudo mount /dev/mapper/cryptenroll-lab "$MNT"
echo ok | sudo tee "$MNT/proof" >/dev/null
# Enroll recovery + TPM (if present) while passphrase still works
sudo systemd-cryptenroll --recovery-key "$LOOP"
if systemd-cryptenroll --tpm2-device=list 2>/dev/null | grep -q .; then
sudo systemd-cryptenroll --tpm2-device=auto "$LOOP"
fi
sudo cryptsetup luksDump "$LOOP"
# Cleanup
sudo umount "$MNT"
sudo cryptsetup close cryptenroll-lab
sudo losetup -d "$LOOP"
rm -f "$IMG"
On hosts without a TPM, skip the TPM block and practice FIDO2 with a real key, or recovery + password only.
crypttab options worth knowing
From crypttab(5), focused on token unlock:
# TPM2
name UUID=… none tpm2-device=auto,tpm2-pin=yes,x-initrd.attach
# FIDO2
name UUID=… none fido2-device=auto,x-initrd.attach
# PKCS#11
name UUID=… none pkcs11-uri=auto,x-initrd.attach
# Headless server: no password prompt (only after hardware path is proven)
name UUID=… none tpm2-device=auto,headless=true,x-initrd.attach
Related knobs:
-
tpm2-pcrs=— manual PCR list when enrollment metadata is not in the header (unusual if you used cryptenroll defaults) -
tpm2-signature=/tpm2-pcrlock=— signed policy and pcrlock policy paths (auto-search under/etc/systemd,/run/systemd, …) -
tpm2-measure-pcr=yes— measure the activated volume key into PCR 15 (253+), useful so later TPM objects can bind to “this install’s root volume key” -
token-timeout=30s— how long to wait for hardware before password fallback -
_netdev— for network-backed keying (NBDE/Clevis territory); not required for local TPM/FIDO
Initrd checklist (root volume)
- Enroll recovery key; store offline
- Enroll TPM2 and/or FIDO2; leave passphrase slot intact
- Update
/etc/crypttabwithtpm2-device=autoandx-initrd.attach - Ensure initramfs includes systemd cryptsetup TPM/FIDO hooks (distro packages vary:
libtss2,libfido2, cryptsetup token plugins) -
update-initramfs/dracut/mkinitcpio - Reboot once; confirm unlock path in journal:
journalctl -b -u systemd-cryptsetup@* - Only then consider
--wipe-slot=passwordif policy requires it — many operators keep a long passphrase slot forever
Failure modes and recovery
| Symptom | Likely cause | Move |
|---|---|---|
| Drops to passphrase after TPM enroll | crypttab missing tpm2-device=, initrd missing TSS stack, or wrong device |
Fix crypttab; rebuild initrd; check tpm2-device=list in initrd rescue |
| Works until kernel/Secure Boot change | PCR bind (7/11/…) changed | Unlock with recovery; re-enroll TPM with same or signed policy |
| TPM PIN lockout | DA counter | Wait out lockout or reset per site TPM policy; use recovery key |
| FIDO prompts repeatedly | Multiple UV tokens | Unplug extras; prefer single UV token or PKCS#11 |
| Cannot enroll from PKCS#11-only volume | Limitation: PKCS#11 cannot authorize new enrollments | Always keep passphrase, recovery, FIDO2, or TPM2 for roll-over |
Compatibility guarantee from the man page: old enrollments unlock on newer cryptsetup; the reverse is not guaranteed. Match systemd-cryptenroll and systemd-cryptsetup versions when possible; re-enroll after major upgrades if you want new hardening (Argon2id PIN, policyref, …).
Boundary: when to use Clevis/Tang instead
Use Clevis + Tang when unlock should depend on network presence (machine in the right building/VLAN), not on the local TPM. Use cryptenroll TPM2 when the trust anchor is the device (and optional PCRs/PIN). They can coexist as separate LUKS slots; they solve different threat models. Neither replaces off-host backups.
Quick reference
# Discover
systemd-cryptenroll --list-devices # 257+
systemd-cryptenroll --tpm2-device=list
systemd-cryptenroll --fido2-device=list
# Enroll
systemd-cryptenroll --recovery-key "$DEV"
systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7+11 "$DEV"
systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=yes "$DEV" # 262+ Argon2id
systemd-cryptenroll --fido2-device=auto "$DEV"
systemd-cryptenroll --password "$DEV"
# Rotate TPM slots
systemd-cryptenroll --wipe-slot=tpm2 --tpm2-device=auto \
--unlock-tpm2-device=auto "$DEV"
# crypttab core
# name UUID=… none tpm2-device=auto,x-initrd.attach
Sources
- systemd-cryptenroll(1) — enrollment, PCR table, wipe-slot, unlock helpers
-
crypttab(5) —
tpm2-device=,fido2-device=,pkcs11-uri=, measure/timeout/headless options - systemd-cryptsetup@.service(8) — activation unit
- UAPI.7 Linux TPM PCR Registry — authoritative PCR meanings
- Upstream man source: systemd/systemd
man/systemd-cryptenroll.xml - Related:
systemd-measure(1),systemd-pcrlock(8),systemd-tpm2-setup.service(8),cryptsetup(8)
Passphrase-at-boot encryption is better than nothing. LUKS2 plus systemd-cryptenroll is how you keep the confidentiality without making every power cycle a human dependency — as long as recovery keys are real, PCR choices match your update story, and you wipe old slots only after a boot you have watched succeed.
Top comments (0)