Skip to content

TPM and measured boot

Overview

Measured boot seals disk unlock (typically LUKS2) to TPM Platform Configuration Register (PCR) measurements of the boot chain: the volume unlocks only when those PCRs match an enrolled policy. On NixOS with Lanzaboote, the documented path is systemd-pcrlock plus boot.lanzaboote.measuredBoot. Set up Secure Boot and Lanzaboote first; this page covers the measured-boot follow-on only.

Lanzaboote’s guide targets LUKS2. It explicitly does not integrate ZFS or btrfs filesystem-level encryption—you can reuse a managed TPM2 policy yourself, but there is no Lanzaboote integration for those layouts.

Details

Support check. Before enabling, confirm systemd-pcrlock accepts your TPM:

$ /run/current-system/systemd/lib/systemd/systemd-pcrlock is-supported
yes

Anything other than yes means Lanzaboote measured boot will not work on that hardware (TPM not supported by systemd-pcrlock).

Module options. Measured boot needs systemd in the initrd and Lanzaboote’s measured-boot switch:

  • boot.initrd.systemd.enable = true
  • boot.lanzaboote.measuredBoot.enable = true
  • boot.lanzaboote.measuredBoot.pcrs — Lanzaboote’s how-to uses e.g. [ 0 4 7 ]. PCRs 1, 2, and 3 may be flaky depending on hardware; try them only after checking behavior on your machine. Upstream explanation highlights PCR 4 as covering the boot loader and Lanzaboote stub (and thus, via the stub’s checks, kernel/initrd/cmdline)—do not invent further PCR semantics here; see the UAPI PCR registry and Lanzaboote’s measured-boot explanation.

configurationLimit ≤ 8. With measured boot enabled, the maximum allowed configurationLimit is 8. systemd-pcrlock currently will not create a policy for more than eight variants (systemd#41526).

Switch and enroll. Apply with nixos-rebuild boot, reboot into the new generation, then enroll the pcrlock policy into the LUKS2 volume with systemd-cryptenroll. Always keep a recovery passphrase or other recovery unlock path—systemd-pcrlock is still experimental per systemd. For an attended workstation, bind a user PIN as well (--tpm2-with-pin=true). After enroll, Lanzaboote updates measurements and the TPM policy on subsequent nixos-rebuild; you should not need to re-enroll the LUKS2 volume for normal generation updates.

Ephemeral root. If root is wiped each boot (Impermanence), persist boot.lanzaboote.measuredBoot.pcrlockPolicy and boot.lanzaboote.measuredBoot.pcrlockDirectory across reboots (paths as configured / documented upstream).

Separate path: Clevis. NixOS also ships boot.initrd.clevis (clevis.nix): set enable, map devices.<name>.secretFile to a Clevis JWE, and optionally useTang (requires initrd networking for Tang pins). Unlock uses TPM2, Tang, or SSS pins via JWE. That is orthogonal to Lanzaboote measured boot / pcrlock. Soft edge: Clevis wires JWE material into the initrd secret tree; anything that changes initrd content can interact badly with PCR4-style sealing of the stub/initrd chain—treat combining Clevis-in-initrd with PCR4 policies as a sharp edge and verify against current Lanzaboote/systemd docs rather than assuming they compose. Disk unlock is not a substitute for application secrets strategies.

Boundaries (what this page is not)

Examples

Minimal options fragment after Lanzaboote Secure Boot is already working (not a full host config). Enroll LUKS with systemd-cryptenroll separately against the real block device—do not invent device paths into the Nix config.

{
  boot.initrd.systemd.enable = true;
  boot.lanzaboote.measuredBoot = {
    enable = true;
    pcrs = [
      0
      4
      7
    ];
  };
  # With measured boot: keep configurationLimit ≤ 8 (systemd-pcrlock variant limit).
}

Illustrative enroll (run as root against your LUKS partition; keep a recovery passphrase):

systemd-cryptenroll \
  --tpm2-device=auto \
  --tpm2-with-pin=true \
  --tpm2-pcrlock=/var/lib/systemd/pcrlock.json \
  /dev/sdX

See also

References