Stop Booting Unsigned Kernels: Practical Secure Boot with sbctl on Linux
Disk encryption protects data at rest. AppArmor, Landlock, and seccomp constrain running processes. None of that helps if an attacker replaces your bootloader or kernel before Linux starts.
That is the job of UEFI Secure Boot: the firmware only executes EFI binaries whose cryptographic signatures match keys stored in NVRAM. On most PCs those keys are Microsoft’s. That is fine for shipping Windows and vendor-signed Linux shims — and useless if you want to prove your kernel, your UKI, and your boot manager were not swapped on the ESP.
This guide walks through a practical, operator-owned Secure Boot setup with sbctl — create keys, enroll them (safely), sign the boot chain, verify, and keep signatures current after package updates. No theory dump. Commands you can run.
Boundary: this is about your Platform Key / KEK / db ownership and signing the Linux boot chain. It is not firmware CVE patching with fwupd/LVFS, TPM-bound LUKS unlock, or distribution shim/MOK workflows. Those solve adjacent problems.
What you are actually controlling
Secure Boot uses four EFI variables:
| Variable | Role |
|---|---|
| PK (Platform Key) | Root of trust. Only one. Controls who may update KEK. |
| KEK (Key Exchange Key) | Authorized to update db / dbx. |
| db (signature database) | Allowed certificates and/or hashes for EFI binaries. |
| dbx (forbidden database) | Revoked certificates and hashes. |
When PK is empty, firmware is in Setup Mode and you can enroll new keys without the previous PK’s signature. After you enroll a PK, the machine returns to User Mode and key updates must be signed up the chain.
That model is documented clearly in the Arch Wiki Secure Boot page and in Matthew Garrett / Hansen Partnership’s key hierarchy notes.
Prerequisites
- UEFI system (not legacy BIOS / CSM-only).
- Root access and a working ESP mounted (commonly
/boot,/efi, or/boot/efi— check withfindmnt /boot /efi /boot/efiorbootctl status). - Ability to enter firmware setup (Del / F2 / Esc, or
systemctl --firmware-setup rebooton systemd systems). - A recovery plan: USB live medium, firmware “restore factory keys”, and preferably a full backup of current EFI variables before you touch anything.
Install sbctl from your distro when possible:
# Arch
sudo pacman -S sbctl
# Alpine
sudo apk add sbctl
# Fedora (community COPR — verify the repo before enabling)
sudo dnf copr enable chenxiaolong/sbctl
sudo dnf install sbctl
# From upstream source (Go ≥ 1.20)
go install github.com/foxboron/sbctl/cmd/sbctl@latest
Package availability varies; see Repology’s sbctl page and the upstream README.
Optional but recommended for backups:
# Debian/Ubuntu/Arch package name is typically efitools
sudo apt install efitools # or: pacman -S efitools
Step 0 — Baseline and backup (do not skip)
Check current state
sbctl status
Typical “stock / not yet owned” output looks like:
Installed: ✘ Sbctl is not installed
Setup Mode: ✘ Enabled # or Disabled, depending on firmware state
Secure Boot: ✘ Disabled
On systemd systems, bootctl is another independent view:
bootctl 2>/dev/null | sed -n '1,20p'
# Look for: Secure Boot: enabled (user) | disabled (setup) | disabled (disabled)
You can also read the EFI variable directly (final integer 1 means enabled):
od --address-radix=n --format=u1 \
/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c
# Example when enabled: 6 0 0 0 1
Backup the factory variables
mkdir -p ~/secureboot-backup && cd ~/secureboot-backup
for var in PK KEK db dbx; do
efi-readvar -v "$var" -o "old_${var}.esl"
done
ls -l old_*.esl
Copy that directory off-box. If enrollment goes wrong, factory restore in firmware + these files are your parachute.
Step 1 — Enter Setup Mode in firmware
sbctl can only enroll keys while the platform is in Setup Mode (PK cleared).
Exact menus differ by vendor. The reliable pattern, documented in sbctl’s workflow example:
- Reboot into firmware setup.
- Open Secure Boot / Key Management (not merely “OS Type = Other OS”).
- Choose Clear Secure Boot Keys / Delete all keys / Reset to Setup Mode.
- Save and reset. Confirm PK is unloaded after reboot.
Then verify from Linux:
sbctl status
# Expect: Setup Mode: Enabled (wording may show as ✘ Enabled depending on version)
Important: “Custom” OS type alone is not Setup Mode. You need the keys cleared.
Step 2 — Create your keys
sudo sbctl create-keys
Expected shape:
Created Owner UUID a9fbbdb7-a05f-48d5-b63a-08c5df45ee70
Creating secure boot keys...✔
Secure boot keys created!
sbctl stores key material under its state directory (commonly /usr/share/secureboot or /var/lib/sbctl depending on package/version — confirm with your package files). Treat those private keys like root credentials:
- Restrict directory permissions.
- Back them up encrypted offline.
- Do not leave them only on the same ESP you are trying to protect.
Optional: hardware token keys
Upstream supports YubiKey PIV for key creation:
# Creates RSA4096 material in the YubiKey PIV Signature slot
sudo sbctl create-keys --keytype yubikey
# PIN for later sign operations
export SBCTL_YUBIKEY_PIN='your-pin'
Use this when you want signing to require physical presence. Remember the PIN environment variable for hooks that re-sign kernels.
Step 3 — Enroll keys without bricking Option ROMs
This is the step people get wrong.
Some devices (GPU, NVMe, NIC) expose Option ROMs that the firmware executes during boot. Many of those ROMs are signed with Microsoft’s UEFI CA. If you enroll only your keys and drop Microsoft’s certificates from db, the firmware may refuse to initialize that device — black screen, unreachable firmware UI, soft-brick. The Arch Wiki documents this class of failure on certain Lenovo ThinkPad X/P/T and ASUS TUF systems.
Default recommendation for laptops and most desktops
Enroll your keys + Microsoft’s keys:
sudo sbctl enroll-keys -m
Framework and similar OEM firmware tooling
Also keep OEM built-in certificates when you still want vendor firmware utilities:
sudo sbctl enroll-keys -m -f
Lab machines / known-good OpROM inventory only
# Only if you understand OpROM signing on THIS chassis
sudo sbctl enroll-keys
After enrollment:
sbctl status
You want something like:
Installed: ✔ Sbctl is installed
Owner GUID: a9fbbdb7-a05f-48d5-b63a-08c5df45ee70
Setup Mode: ✔ Disabled
Secure Boot: ✘ Disabled # still off until you flip it in firmware
Setup Mode should now be disabled (PK present). Secure Boot itself is often still off until you enable the policy bit and reboot.
Step 4 — Discover what must be signed
sudo sbctl verify
Example output from upstream docs:
Verifying file database and EFI images in /efi...
✘ /boot/vmlinuz-linux is not signed
✘ /efi/EFI/BOOT/BOOTX64.EFI is not signed
✘ /efi/EFI/systemd/systemd-bootx64.efi is not signed
✘ /efi/EFI/arch/fwupdx64.efi is not signed
✘ /efi/EFI/Linux/linux-linux.efi is not signed
Your paths will differ. Typical targets:
- Boot manager:
systemd-bootx64.efi,grubx64.efi, orBOOTX64.EFI - Kernel image:
/boot/vmlinuz-*(when the firmware or bootloader authenticates it) - Unified Kernel Image:
/boot/EFI/Linux/*.efior/efi/EFI/Linux/*.efi - fwupd EFI capsule helper:
fwupdx64.efi(and sometimes the.signedsibling path)
Step 5 — Sign and record files in sbctl’s database
The -s flag signs and saves the path so future sign-all / package hooks can re-sign automatically.
# Examples — adjust to sbctl verify output on YOUR host
sudo sbctl sign -s /efi/EFI/systemd/systemd-bootx64.efi
sudo sbctl sign -s /efi/EFI/BOOT/BOOTX64.EFI
sudo sbctl sign -s /boot/vmlinuz-linux
# fwupd helper often needs an explicit output path used by the package
sudo sbctl sign -s /usr/lib/fwupd/efi/fwupdx64.efi \
-o /usr/lib/fwupd/efi/fwupdx64.efi.signed
Bulk helper when verify lists many absolute paths:
sudo sbctl verify | sed -E 's|^.* (/.+) is not signed$|sbctl sign -s "\1"|e'
Inspect the signing database:
sudo sbctl list-files
sudo sbctl sign-all # re-sign everything already enrolled in the DB
Prefer UKIs when you can
A Unified Kernel Image packs kernel + initramfs + cmdline (+ microcode) into one signed EFI binary. That keeps the Secure Boot chain tight: firmware verifies one object instead of trusting an intermediate loader to hand-wave an unsigned initramfs.
sbctl can bundle UKIs, but upstream calls this a second-class path and recommends your initramfs generator instead:
-
mkinitcpio--uki -
dracut--uefi -
ukifyfrom systemd
Example with sbctl’s bundle helper (illustrative):
sudo sbctl bundle -s \
-i /boot/intel-ucode.img \
-k /boot/vmlinuz-linux \
-f /boot/initramfs-linux.img \
/efi/EFI/Linux/linux-linux.efi
sudo sbctl list-bundles
sudo sbctl generate-bundles # rebuild all registered bundles
For day-to-day operations, wire UKI generation into your distro’s kernel install path, then sbctl sign -s the resulting .efi.
Step 6 — Enable Secure Boot and prove it
- Reboot into firmware setup.
- Enable Secure Boot.
- Boot Linux.
Verify from userspace:
sbctl status
# Secure Boot: ✔ Enabled
bootctl 2>/dev/null | grep -i 'Secure Boot'
# Secure Boot: enabled (user)
# Kernel view (capable boot path)
journalctl -kg 'secure boot' -b --no-pager
# Expect: Secure boot enabled
If the machine fails to boot:
- Firmware → temporarily disable Secure Boot or restore factory keys.
- Boot a live USB, mount the ESP + root, chroot if needed,
sbctl verify/sbctl sign-all. - Confirm you did not drop Microsoft keys on hardware that needs them (
enroll-keys -m).
Keep signatures alive after updates
Signing once is not enough. The next kernel package will drop an unsigned vmlinuz and break boot the moment Secure Boot is enforcing.
Arch Linux: pacman hook
sbctl ships a pacman hook that re-signs enrolled files when the kernel, systemd, or bootloader packages update. After install it is usually active automatically — confirm:
pacman -Ql sbctl | grep -E 'hook|pacman'
Caveat (systemd-boot): if you use systemd-boot-update.service, the bootloader binary may be updated on reboot, after the pacman hook already ran. Work around by signing the staged binary path as well, or by running bootctl update + sbctl sign -s in a controlled maintenance window before rebooting with Secure Boot on. The Arch Wiki calls this out under the sbctl section.
Debian / Ubuntu style: kernel hook sketch
There is no single universal Debian package hook for sbctl on every release. A minimal, explicit approach:
# /etc/kernel/postinst.d/zz-sbctl
#!/bin/sh
set -e
command -v sbctl >/dev/null 2>&1 || exit 0
sbctl sign-all || true
# Also sign the versioned kernel if your layout needs it:
# sbctl sign -s "/boot/vmlinuz-${1}"
sudo install -m 0755 /etc/kernel/postinst.d/zz-sbctl /etc/kernel/postinst.d/zz-sbctl
# Mirror under /etc/kernel/postrm.d only if you intentionally clean signatures
Test after the next apt upgrade that installs a kernel:
sudo sbctl verify
sudo sbctl list-files
Automation checklist
| Event | Action |
|---|---|
| Kernel upgrade |
sbctl sign-all / hook |
| systemd-boot upgrade | sign systemd-bootx64.efi (watch delayed bootctl update) |
| fwupd EFI helper upgrade | re-sign fwupdx64.efi → .signed
|
| New UKI build | sbctl sign -s /path/to/uki.efi |
| Key compromise |
sbctl rotate-keys (plan downtime; understand firmware write rules) |
Operational hardening around Secure Boot
Secure Boot is necessary, not sufficient.
- Firmware admin password — otherwise “clear keys” is a physical-access freebie.
-
Full-disk encryption — Secure Boot authenticates code; it does not encrypt
/. - Protect sbctl private keys — offline encrypted backup; consider YubiKey keytype.
-
ESP integrity monitoring — unexpected changes to
*.efi/vmlinuz*deserve alerts (AIDE, audit watches, or your existing FIM). - Measured boot / TPM policy (optional next layer) — bind disk unlock or attestation to PCR state once the boot chain is stable.
- Know your recovery story — factory key restore path tested once on purpose.
Troubleshooting quick hits
| Symptom | Likely cause | Fix |
|---|---|---|
enroll-keys fails |
Not in Setup Mode | Clear PK in firmware, confirm sbctl status
|
| Black screen after enabling SB | Missing Microsoft/OEM certs for OpROM | Re-enter Setup Mode; enroll-keys -m (and -f if needed) |
| Boots with SB off, fails with SB on | Unsigned bootloader/kernel/UKI |
sbctl verify → sign -s → sign-all
|
Breaks after pacman -Syu
|
Hook missed new path |
sbctl verify, add missing files with sign -s
|
bootctl says disabled, firmware says on |
Boot path did not expose SB to kernel | Use a SB-aware loader; check journalctl -kg 'secure boot'
|
| Dual-boot Windows fails | Windows Boot Manager not trusted | Keep Microsoft keys (-m); do not delete vendor Windows certs from db
|
What success looks like
sbctl status
# Installed ✔ | Setup Mode disabled | Secure Boot enabled
sudo sbctl verify
# All listed ESP/boot chain files ✔ signed
bootctl | grep -i 'Secure Boot'
# enabled (user)
At that point the firmware — not hope — is deciding whether your boot binaries are allowed to run.
Sources and references
- sbctl upstream README and command reference
- sbctl example enrollment workflow (firmware screenshots)
- Arch Wiki — UEFI/Secure Boot (sbctl section, OpROM warnings, verification)
- Rod Smith — Managing EFI Boot Loaders: Secure Boot
- Hansen Partnership — The meaning of all the UEFI keys
- systemd Boot Loader Interface (automatic boot-chain integration roadmap in sbctl)
- Repology — sbctl packages
Secure Boot stops being a Windows checkbox the moment you hold the Platform Key and every file on the ESP is in your signing database. sbctl makes that operational instead of ceremonial — create, enroll, sign, verify, re-sign on upgrade. Do the Microsoft/OEM enrollment flags on real hardware, keep an offline key backup, and test one controlled failure recovery before you need it at 2 a.m.
Top comments (0)