NixOS: Installation Guide with RAID 1, encryption, and TPM Unlock (part 4 - Secure Boot)

Content:
In the fourth post of our series, we are going to configure Secure Boot to ensure that only trusted operating systems can be executed.
This is the fourth post of the series:
- Preparing the virtual machine and partitioning the disks
- Disko, LUKS, and btrfs
- Installing the OS
- Enabling Secure Boot (this post)
Secure Boot is a fundamental component for tightening the security, especially when using the TPM to automatically decrypt the disk. I will explain what it is, what it’s for, and how to implement it in NixOS using Lanzaboote.
Understanding Secure Boot
Secure Boot is a protocol that is part of the UEFI (Unified Extensible Firmware Interface). When enabled, it verifies if the operating system’s bootloader has a valid digital signature recognized by the keys registered in the motherboard’s firmware.
This prevents boot-level malware (rootkits) or unauthorized operating systems from loading. Additionally, the TPM uses the Secure Boot state as one of the metrics to decide whether or not to release the disk encryption keys.
There are three main ways to manage Secure Boot keys:
- Factory default keys: Usually the Microsoft keys that come with almost every computer;
- Custom keys (User Mode): You remove the Microsoft keys and install only your own;
- Hybrid Mode: You keep the Microsoft keys (for dual-booting with Windows) and add your own.
If you don’t intend to run Windows, the second option is the most restrictive and secure. If you need dual boot, the third is mandatory. It’s worth noting that NixOS does not have a bootloader signed by Microsoft (unlike Ubuntu or Fedora), so we always need to generate our own keys.
And why Microsoft keys, specifically? Because they were the first company to push Secure Boot as mandatory, and they hold the largest share of the PC market. There is quite a bit of controversy regarding this.
The important thing for our setup is knowing that once you define how Secure Boot will be used, it cannot be changed without breaking the automatic decryption process (which will be done with the help of the TPM). In our case, we will use the third option (mixing Microsoft keys with our own). If Secure Boot is turned off on the machine or a new platform key is loaded, the TPM will refuse to decrypt the disk. I’ll explain this in future posts, so for now, what matters is enabling Secure Boot.
Putting the Firmware into Setup Mode
In the first post, we cleared the keys in the VM’s boot menu to enter Setup Mode. This is essential: for us to write our keys via software, the firmware must be “open” to new signatures.
Confirm the status by running:
$ sudo bootctl status
...
Secure Boot: disabled (setup)
...
If disabled (setup) appears, we are ready to proceed.
Generating Keys with sbctl
We will use sbctl to create and register our keys. Since it isn’t installed by
default, let’s use nix-shell:
nix-shell -p sbctl
Note: In NixOS, you can bring any application into your terminal’s context using nix-shell. With this, the sbctl
command becomes available in a subshell — to exit, just run exit.
To generate the keys, we will run the create-keys subcommand:
sudo sbctl create-keys
The keys will be generated in /var/lib/sbctl/. You can see the structure with the tree command:
$ tree /var/lib/sbctl/
/var/lib/sbctl/
├── GUID
└── keys
├── db
│ ├── db.key
│ └── db.pem
├── KEK
│ ├── KEK.key
│ └── KEK.pem
└── PK
├── PK.key
└── PK.pem
Install the keys into the machine’s firmware with enroll-keys. This will use the keys we created earlier with sbctl.
With the --microsoft argument, Microsoft’s platform keys will also be installed:
sudo sbctl enroll-keys --microsoft
Verify that Secure Boot has been re-enabled with:
sudo bootctl status
It should show Secure Boot: enabled (user). It might not show as enabled yet until the next boot, showing up as
Secure Boot: disabled, but without setup. But don’t reboot just yet!
Signing the Boot with Lanzaboote
At this point, if you restart the machine, NixOS will no longer boot, as the NixOS bootloader is not yet signed with the
Secure Boot keys we created in the previous step. That’s where Lanzaboote
comes in. It automates the signing of NixOS generations. It replaces the default systemd-boot manager with one that
handles the keys generated by sbctl. Once enabled, whenever a new bootloader is created in the /boot directory, it
will be signed with the keys located in /var/lib/sbctl.
To enable Lanzaboote and then sign the files, first go to the configuration directory that was copied in previous posts
to /etc/nixos, and check out the correct commit. Be careful not to forget the git checkout, or it won’t work:
cd /etc/nixos
# ensuring the current user owns the files:
sudo chown -R giggio:users .
# checkout with the message "Enable lanzaboote":
git checkout dab0fd4
The change in your configuration.nix disables the native systemd-boot and activates lanzaboote, pointing to the
keys directory:
boot.loader.systemd-boot.enable = false;
boot.lanzaboote = {
enable = true;
pkiBundle = "/var/lib/sbctl";
};
The Lanzaboote configuration was already present; it was just disabled. And with it enabled, systemd-boot can be
disabled. Note also that the keys directory, defined in the pkiBundle attribute, is already correctly pointed to
/var/lib/sbctl.
That’s all; now just install the new configurations, which take effect immediately and will also be effective on the next boot:
sudo nixos-rebuild switch
Validating the Signature
To ensure Lanzaboote did its job and signed the .efi binaries, run:
$ sudo sbctl verify
Verifying file database and EFI images in /boot...
✓ /boot/EFI/BOOT/BOOTX64.EFI is signed
✓ /boot/EFI/Linux/nixos-generation-1-xxx.efi is signed
✓ /boot/EFI/Linux/nixos-generation-2-yyy.efi is signed
✗ /boot/EFI/nixos/kernel-7.0-zzz.efi is not signed
✓ /boot/EFI/systemd/systemd-bootx64.efi is signed
Note about the Kernel: It is normal to see an ✗ on the isolated kernel file. What matters are the files inside
/boot/EFI/ and the boot managers. Lanzaboote creates a Unified Kernel Image (UKI), which bundles the kernel and
the initrd into a single signed executable that the UEFI can run directly.
Now you can reboot. If everything went well, the system will come up normally and bootctl status will confirm: Secure Boot: enabled (user).
The machine still asks for the password to decrypt the disk. This is expected, as we haven’t yet implemented the structure for automatic decryption with the TPM.
Troubleshooting Secure Boot
If something goes wrong and the VM won’t start, you can reset the NVRAM (the VM’s “BIOS”). On your host, turn off the VM and run:
# on the host:
virsh start <vm_name> --reset-nvram
This will start the virtual machine normally, but with all the firmware boot settings lost, including Secure Boot settings.
The following commands will re-apply the boot settings to the firmware’s non-volatile memory and the disk:
# on the vm:
# registering platform keys in the firmware again:
sudo sbctl enroll-keys --microsoft
# installing the boot menu option:
sudo bootctl install
# re-signing the UKIs:
sudo nixos-rebuild switch
Why run switch again: When running bootctl install, the files /boot/EFI/systemd/systemd-bootx64.efi and
/boot/EFI/BOOT/BOOTX64.EFI will not be signed, so it is necessary to run switch so that these two files become
signed again.
Declarative Approach
Lanzaboote allows generating platform keys automatically via Nix config:
boot.lanzaboote = {
autoGenerateKeys.enable = true;
autoEnrollKeys = {
enable = true;
autoReboot = true;
};
};
This is described in more detail in the docs on automatic provisioning.
While practical for testing, I don’t recommend using this in production systems. The ideal approach is to generate keys
manually (as I demonstrated here) and store them securely somewhere. A good option is to use
sops-nix, keeping the keys encrypted within the repository itself. The
inconvenience would be during application, where you would always need the keys or devices to decrypt the sops secrets
at every nixos-rebuild.
Next Up: Using TPM to Decrypt the Disk
Secure Boot is active. The disk is encrypted. But we still have to type the LUKS password at every boot.
If someone tries to start another operating system, it will need to be signed with your platform key or Microsoft’s. You
can make the system more secure by removing the --microsoft option from the enroll-keys command and not allowing any
other operating system.
In the next post, we will configure the TPM (Trusted Platform Module) so that it “measures” the Secure Boot state and releases the encryption key automatically if the system hasn’t been tampered with. Maximum security with convenience.
See you then!