Home / Docs / Measured boot

Don't trust,
verify.

A Blackbox VM with measured boot never boots from its own disk. The firmware, the kernel, the initramfs that asks for your passphrase and the command line that carries your SSH key are all part of the AMD SEV-SNP launch measurement, and every one of them is rebuilt byte for byte from public sources. This page is how you check.

The hole this closes01

SEV-SNP encrypts a VM's memory with a key the host never holds, and the CPU signs a launch measurement: a hash of everything loaded into the VM before it started. On an ordinary confidential VM that "everything" is the firmware and nothing else. The bootloader, the kernel and the initramfs live on the guest's own unencrypted /boot partition, which the host can rewrite whenever it likes. The initramfs is the program that asks for your LUKS passphrase. A host that swaps it for one that also emails the passphrase home has defeated the disk encryption without ever touching encrypted memory. That is the standard state of confidential VPS hosting, ours included until this release.

Measured boot removes the guest's disk from the boot path entirely. QEMU hands the firmware a kernel, an initramfs and a command line over fw_cfg with kernel-hashes=on; their SHA-256 hashes are written into a page that is part of the launch measurement, and the firmware is the AmdSevX64 build of OVMF, the one build that refuses to run a blob whose hash is not in that page and that cannot boot from disk at all. The page is filled by the host at launch, so the firmware check alone proves nothing to you; what proves it is that the page is measured, and the measurement is signed by the CPU. Change one byte of the initramfs and the launch measurement changes, and the CPU-signed report says so; nothing the host does can produce the published number for anything but the published bytes. Change nothing and the measurement is a number you can compute at home.

What is measured02

  • Firmware: OVMF.amdsev.fd, built from edk2 OvmfPkg/AmdSev/AmdSevX64.dsc at a pinned tag with a pinned Debian toolchain.
  • Kernel: vmlinuz, extracted unmodified from Debian's signed linux-image package. The package hash is in the release manifest; fetch the same package from snapshot.debian.org and compare.
  • Initramfs: initrd.img, built with mkinitramfs from packages pinned to one snapshot.debian.org timestamp plus the scripts in the source tree. Two builds give identical bytes.
  • Command line: the exact string shown on your server's panel. It carries cryptopts= (where the LUKS root is), ip=, and bb.* parameters: your SSH public key, your addresses, your hostname and the instance id. Because it is measured, nobody can add a key to it without changing the number you check.
  • vCPU state: one initial register file per vCPU, so the count matters; the panel states it.
  • Guest policy: 0x30000: SMT allowed, debugging off, no migration agent. The verifier rejects anything else.

Not measured, on purpose: your root filesystem, which is yours and encrypted with a passphrase we never see. No seed ISO is attached to a measured VM at all; the initramfs writes the cloud-init seed from the measured command line.

How the initramfs proves itself03

The initramfs is public and identical for every customer, so it cannot carry an SSH host key. On every boot it generates a fresh ed25519 key, requests an SNP attestation report from the CPU with REPORT_DATA set to the SHA-256 of that key, and serves the report in the SSH banner and through the attest command. OpenSSH shows you that same SHA-256 as the host key fingerprint. So: if the report's signature chains to AMD's root, its measurement equals the one you computed, and its REPORT_DATA equals the fingerprint your client just displayed, then the machine holding that key is a genuine SNP guest running exactly this image, and nothing in between can read what you type into it.

Every SSH login into the initramfs lands in a forced command with four verbs: attest (the report and its context as JSON), report (the raw report), unlock and encrypt. Password logins are disabled at build time; the only key allowed in is the one on the measured command line.

Verify before the passphrase04

On your own computer, never on the server: any machine with Python 3, OpenSSH and internet access will do (the verifier fetches your VM's VCEK certificate from AMD's key server). The server is locked in its initramfs until this passes and you send the passphrase over SSH.

pip install sev-snp-measure cryptography
curl -sSO https://servury.com/downloads/blackbox/blackbox-verify
REL=2026.09
mkdir $REL && cd $REL
for f in OVMF.amdsev.fd vmlinuz initrd.img RELEASE.json SHA256SUMS; do curl -sSO https://servury.com/downloads/blackbox/$REL/$f; done
sha256sum -c SHA256SUMS && cd ..

python3 blackbox-verify --host YOUR.VM.IP --release ./$REL --vcpus 2

The verifier fetches the attestation over SSH with a throwaway known-hosts file, keyscans the host key itself, parses the report, checks the policy, checks that REPORT_DATA is the SHA-256 of the key on the wire, recomputes the launch measurement from the release directory and the command line the VM reports, and verifies the ECDSA P-384 signature against the VCEK certificate chained to AMD's ASK and ARK. It prints PASS and a known_hosts line you can pin for this boot, or FAIL with the reason. On FAIL, do not type a passphrase; the VM is not what it claims to be, and we would like to hear about it.

Nothing here relies on the panel. The panel shows the expected measurement and the command line for convenience, but the verifier computes both from what you downloaded and what the VM said, and the vCPU count is the only thing you take from us, which a wrong value can only make fail.

One detail for anyone scripting this themselves: the firmware's kernel loader appends initrd=initrd to what the guest sees in /proc/cmdline. The hash in the measurement covers the command line as QEMU passed it, without that suffix, and the verifier strips it.

By hand, without the tool: sev-snp-measure --mode snp --vcpus N --vcpu-type EPYC-v4 --guest-features 0x1 --ovmf OVMF.amdsev.fd --kernel vmlinuz --initrd initrd.img --append '<command line>' must print the measurement field of the report, and snpguest verify or any SNP report verifier of your choice checks the signature.

First boot: encrypt05

A measured VM boots straight into the initramfs and waits. Nothing from its disk has run. Verify as above, then:

ssh root@YOUR.VM.IP encrypt

The script asks for a passphrase once (and its confirmation), checks the filesystem, shrinks it to make room for the LUKS2 header, encrypts the root partition in place (aes-xts-plain64, 512-bit key, argon2id), grows the partition and the LUKS volume to the whole disk, expands the filesystem, writes crypttab and fstab inside the encrypted root, and then lets the boot continue straight into your system with the volume it just opened. It refuses to claim success unless LUKS is really on disk, restores the filesystem size if anything fails before encryption, and resumes an encryption that was interrupted by a dropped connection. Choose a passphrase you can keep: it cannot be recovered by anyone, including us.

Every later boot stops in the initramfs with a new host key: verify again, then ssh root@YOUR.VM.IP unlock, enter the passphrase, and the system boots. Once running, the machine is a normal Debian or Ubuntu with your SSH key from the measured command line, its network configured by cloud-init from the seed the initramfs wrote, and a copy of the boot-time report at /run/blackbox/attest.json.

Type the passphrase only over SSH after a PASS. The serial console and the VNC console pass through the host like on any VM; they are there for you to watch the boot, not to type secrets into.

Rebuild everything yourself06

The source tree for every release is published next to the artifacts (blackbox-images-RELEASE.tar.gz). On any Debian 13 machine with root and mmdebstrap:

tar xzf blackbox-images-$REL.tar.gz && cd blackbox-images
sh build/mkchroot.sh                       # pinned snapshot.debian.org chroot
git clone --depth 1 --branch edk2-stable202505 https://github.com/tianocore/edk2 /var/tmp/bb/src/edk2
git -C /var/tmp/bb/src/edk2 submodule update --init --depth 1 --recursive
cp build/build-firmware.sh build/build-initramfs.sh /var/tmp/bb/chroot/usr/local/bin/
build/in-chroot.sh sh -c 'apt-get update -qq && cd /out && mkdir -p pkgs && cd pkgs && apt-get download linux-image-KVER'
build/in-chroot.sh build-firmware.sh /out/firmware
build/in-chroot.sh build-initramfs.sh /out/initramfs
sha256sum /out/firmware/OVMF.amdsev.fd /out/initramfs/vmlinuz /out/initramfs/initrd.img

The three hashes must equal the ones in RELEASE.json. The manifest records the snapshot timestamp, the edk2 commit, the kernel package and its hash, and the SOURCE_DATE_EPOCH, which is everything that determines them. If you get a different byte, that is a bug in our recipe or evidence against us, and either way we want the report.

Bring your own image07

Rebuilding our release proves our sources are honest. Booting your own removes the question. On a measured Blackbox you can replace the release kernel, initramfs and command line with files you built, and the machine measures those instead: the expected measurement becomes a function of the public firmware, your bytes and your command line, and nothing of ours runs inside the guest at all.

Two shapes are accepted. A UKI, one PE file with the kernel, the initramfs and the command line inside it, built with ukify or objcopy against the systemd stub; it boots as-is, and the command line you enter in the panel may be left empty so the one in its .cmdline section applies. Or a bzImage with a separate initramfs and the command line typed in the panel. A UKI cannot take a separate initramfs: QEMU refuses one for a kernel without the boot-protocol header.

# a UKI from your own kernel, initramfs and command line
ukify build --linux vmlinuz --initrd initrd.img \
  --cmdline 'console=ttyS0,115200 ip=YOUR.IP::GATEWAY:255.255.255.0:host:ens18:off' \
  --output blackbox.efi
sha256sum blackbox.efi

Put the file on any http(s) server and give the panel its URL and SHA-256 (Manage tab, Boot image). The node downloads it into the machine's own storage, refuses it if the hash differs from what you declared, and re-hashes it at every start before QEMU sees the path. A restart boots your image; Back to the Servury release undoes it. A reinstall always comes back on the release, because it replaces the disk.

Your image has to bring everything the release initramfs used to do: configure the network (the panel lists the addresses; the kernel's ip= syntax on the suggested line is enough for IPv4), fetch and expose an attestation report (/dev/sev-guest, for instance with snpguest report), and decide what to do with the disk, which is untouched: unlock your LUKS volume, write an image of your own over /dev/sda, or ignore it and run from RAM. Verification is the same arithmetic as for our release, with your files:

sev-snp-measure --mode snp --vcpus N --vcpu-type EPYC-v4 --guest-features 0x1 \
  --ovmf OVMF.amdsev.fd --kernel blackbox.efi --append ''

The Overview card prints this command filled in for your machine, and our own expected value to compare against. Limits: kernel file up to 1 GiB and at least 128 KiB (QEMU may split up to that much off the front of a non-bzImage file as "setup"; the firmware puts it back), command line up to 4096 bytes, http(s) only. Confidential guests only: the firmware stays the AmdSevX64 build of the release you are on, so its hash is the one number on the card that is still ours, and it is reproducible from section 6.

Bring your own disk08

The template you are installed from and the console you could install by hand over are both ours, and both could in principle be tampered with. So neither has to be used. Build the disk at home, pre-installed and pre-encrypted, and have the node write it over the machine's disk: Manage tab, Disk image, a URL and a SHA-256. Raw or qcow2 (qcow2 may be internally compressed; archives are not unpacked), up to the plan's disk size, self-contained: a qcow2 that points at a backing file or an external data file is refused, since those are paths on our host.

# a pre-encrypted image, built anywhere: partition, LUKS, install, then ship the whole disk
qemu-img convert -O qcow2 -c blackbox.raw blackbox.qcow2
sha256sum blackbox.qcow2

What the node does: downloads the file into the machine's own storage, checks the hash, stops the machine, snapshots and discards the disk (so nothing of the previous contents survives in blocks the image does not cover), writes the image with qemu-img convert, and starts the machine again. If anything fails, the snapshot is rolled back and the disk is exactly as it was. A reinstall replaces the disk with a template again.

With measured boot, pair it with section 7: your UKI carries the initramfs that unlocks your LUKS volume, and the disk it unlocks is the one you wrote. Then the only code that ran on this machine which you did not build is the firmware, whose hash you can reproduce, and the CPU signs a measurement of the rest. Without measured boot the firmware boots the disk's own EFI loader, so an image with its own unlock path (dropbear in the initramfs, for instance) works on its own; the seed CD with your SSH key and addresses stays attached, and a cloud-init image will use it while anything else can ignore it.

What this does not protect09

  • The template before you encrypt it. Your root filesystem starts as our unencrypted Debian or Ubuntu image. Until you run encrypt, the host can read and write it, and a host that pre-installed something in the template would have it encrypted along with the rest. Encrypt at first boot, before the rootfs has ever run, and audit the image afterwards with debsums or your own tooling; the template is a stock cloud image plus the packages listed in the build script. Or skip the template entirely: section 8.
  • The console. Serial and VNC pass through the host. They can watch; do not type secrets into them.
  • Disk integrity. LUKS2 with XTS hides your data but does not authenticate it; a host can corrupt ciphertext blindly. It cannot read or forge them meaningfully.
  • Metadata. Traffic volume, packet headers, CPU usage, and the fact that the VM exists are visible to us as to any transit provider. Encrypted memory is not anonymity; that is what the rest of Servury is for.
  • Availability. We can stop or delete the machine. We cannot read it.
  • Kernel updates are ours, unless you bring your own. The kernel is supplied by the release, so apt upgrade inside the guest does not change what boots. New releases are published here with their hashes; the initramfs lends the guest its module tree when the guest's own does not match the running kernel. Section 7 is the way out of even that.
  • Measured boot requires an IPv4 address for the initramfs network and a Linux template that supports SEV-SNP and encryption.

Releases10

2026.09

current

Built 2026-09-05. Kernel 6.12.107+deb13-amd64 (linux-image-6.12.107+deb13-amd64_6.12.107-1_amd64.deb). Firmware edk2 edk2-stable202505 unknown. Debian snapshot 20260904T000000Z.

OVMF.amdsev.fd8731846cdbcbb6aa5c624238b839544b1553f4c5af874450f043321b3ef8958b
vmlinuz2b2358b37674d2505350528875bb17afae2a36522a9e8a9417eaca65a7da0e08
initrd.img46689fa645887affb5df1fbc7eb9c7253ea7444cc426e07fe17ab2b7804192e5
RELEASE.jsonSHA256SUMS · source tree

The verifier itself: blackbox-verify (Python, MIT). Questions, findings and mismatches: support, PGP key on the usual page.