> For the complete documentation index, see [llms.txt](https://docs.ionos.com/cloud/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ionos.com/cloud/compute-services/compute-engine/confidential-computing-vm/how-tos/hardened-cvm-kernel-howto.md).

# Build a Hardened Kernel for IONOS CLOUD Confidential VMs

Build a minimal Linux kernel for IONOS CLOUD Confidential VMs that removes console, input, serial, USB, and management channel drivers at compile time.

{% hint style="info" %}
**Note:** Validated on Debian 13 (Trixie), kernel 6.12.101, using a standard <code class="expression">space.vars.ionos\_cloud</code> VM in August 2026.
{% endhint %}

When running a Confidential VM (CVM) using AMD SEV-SNP, attestation proves **what** software is running within the VM. However, attestation alone does not prevent an untrusted or curious hypervisor from interacting with the guest through exposed virtual devices, such as serial consoles, USB controllers, and host-guest sockets that the guest operating system (OS) has drivers to support.

The stronger guarantee is that **the guest kernel contains no driver code that can use the interfaces**. It isn't about the hypervisor promising not to attach a serial port; even if a serial port is presented, no code path exists in the guest to interact with it. When an attestation verifier checks the SEV-SNP measurement, it validates a kernel image that enforces this boundary at compile time.

This guide walks through building a hardened kernel for a <code class="expression">space.vars.ionos\_cloud</code> VM. The same approach applies to standard (non-CVM) VMs where you want an auditable, minimal attack surface without relying on runtime configuration.

## Guest isolation guarantees

* **Dark console:** The display remains blank after the EFI handoff. The VM runs, the network operates, and SSH functions, but no local console output is rendered.
* **Dead keyboard:** No keyboard or input drivers exist in the kernel. Even if the hypervisor presents a virtual keyboard, the guest lacks the code paths to receive keystrokes.
* **No management channels:** Serial ports, `virtio-console`, and `vsock` interfaces are removed. Unnecessary and potentially dangerous drivers are removed at compile time; only the drivers the guest needs to operate (networking and storage) stay active. See [What this does not protect against](#what-this-does-not-protect-against) below for the full list of residual bound drivers.
* **API-based shutdown:** As interactive input is disabled, stop the VM using the <code class="expression">space.vars.ionos\_cloud</code> API or the stop option in the DCD. ACPI support stays enabled in the kernel so the guest can handle an ACPI power event if the host sends one, allowing `systemd` to initiate a clean shutdown. <code class="expression">space.vars.ionos\_cloud</code> does not guarantee ACPI delivery on every stop or reset — this is a best-effort clean shutdown path, not a guaranteed one.

{% hint style="info" %}
**A snapshot in time:** Validated on Debian 13 (Trixie) with kernel 6.12.101 in August 2026. These concepts apply to other Linux distributions and kernel versions. Specific configuration options, defaults, and dependencies vary by platform — re-validate them for your target environment. Treat it as a starting point, not a copy-paste recipe.
{% endhint %}

{% hint style="warning" %}
**Regulatory and Security Operations:** This section covers a practical baseline for reducing the VM kernel attack surface. Depending on your regulatory environment, you may need additional controls, such as formal threat modeling, air-gapped build pipelines, full module removal (`CONFIG_MODULES=n`), Kernel Self-Protection Project (KSPP) hardening options, and a custom minimal initramfs. This guide focuses mainly on the kernel build and installation mechanics.
{% endhint %}

## Architecture

Building a hardened kernel happens on a system that is separate from the Confidential VM (CVM) that will eventually run it.

* **Kernel build system:** A standard Compute instance — not a Confidential VM, and not necessarily an IONOS VM at all. Use a VM created just for this purpose, or an existing system you already use for kernel builds. This guide uses a standard <code class="expression">space.vars.ionos\_cloud</code> Compute Engine VM running Debian 13 (Trixie) as the build system; the resulting kernel and configuration approach are not specific to that choice.
* **Transfer:** Once the hardened kernel package and its initrd are built, transfer both to the image factory that produces confidential images for your environment.
* **Attestation / image factory (for example, snp-guard):** The image factory incorporates the kernel and initrd into the artifacts partition of a new confidential image, alongside the other components that make up the attestation measurement (firmware, kernel command line).
* **Deployment:** The resulting confidential image is imported into <code class="expression">space.vars.ionos\_cloud</code> as a standard IaaS image, ready to start as a Confidential VM.

![Architecture: building and deploying a hardened kernel for Confidential VMs](https://1737632334-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MifAzdGvKLDTtvJP8sm%2Fuploads%2Fgit-blob-aa05007359e968498eef186f5e7cb2c9ffb5eb66%2Fhardened-kernel-build-architecture.jpg?alt=media)

{% hint style="info" %}
**Scope of this guide:** The build, configuration, and verification steps below (GRUB, `grub-reboot`, in-guest restart) run on the build system described above — a standard Compute VM, not a CVM. The CVM-specific deployment path (the artifacts partition, kernel command line measurement, and image import) is covered by your image factory's own documentation (for example, snp-guard) and is referenced but not duplicated here.
{% endhint %}

## Overview

The kernel acts as a hardware allowlist. Even if the hypervisor presents unexpected devices, the guest kernel ignores them and prevents communication unless explicit drivers and configurations exist.

```
Host presents virtual hardware:
  USB controllers    →  no USB stack compiled in          →  USB keyboard/storage: no driver
  Serial UART        →  no serial driver                  →  no serial console channel
  virtio-console     →  no driver                         →  no host↔guest serial channel
  virtio-input       →  no driver                         →  no virtual keyboard/mouse
  vsock              →  no driver                         →  no VM socket channel
  VGA/framebuffer    →  framework present, no console      →  screen stays dark (EFI mode)
  ACPI power button  →  ACPI driver present (required)    →  clean shutdown possible if host sends it (best effort, not guaranteed)

Host presents required virtual hardware:
  virtio-net NIC     →  driver present                    →  network works (TLS-protected)
  virtio-blk disk    →  driver present                    →  storage works (LUKS-protected)
```

### The framework approach

Completely disabling kernel subsystems (`CONFIG_VT=n`, `CONFIG_INPUT=n`) causes the standard Debian initramfs to stop responding during startup. Its `udevadm settle` process waits indefinitely for PNP events from the i8042 PS/2 controller that never complete because no drivers exist to process them.

A more surgical approach keeps the core kernel frameworks (Virtual Terminal (VT), Input Subsystem (INPUT), Human Interface Device (HID), Frame Buffer (FB)) intact for initialization coherence while removing every concrete hardware driver. Retaining a single console driver, `CONFIG_VGA_CONSOLE=y` in text mode, allows `systemd` to successfully open `/dev/console` at startup without exposing display output or accepting input on the KVM interface.

## Before you begin

You need a standard <code class="expression">space.vars.ionos\_cloud</code> VM, also called the **build VM**, to compile the kernel. This is separate from your target VM. The build VM:

* Runs a Debian 13 (Trixie), matching the target OS
* Has at least 4 GB RAM and 2 vCPUs (more CPUs = faster build; 16 vCPUs takes approximately 6 minutes)
* Has at least 20 GB free disk space for kernel source and build artifacts

The resulting `.deb` packages are installed on your target VM or fed into your image factory pipeline.

{% hint style="warning" %}
**Important:** Ensure the SSH key authentication works from a separate terminal before rebooting into the hardened kernel. You will have no console fallback once the hardened kernel is running.
{% endhint %}

## Procedure

{% stepper %}
{% step %}

### Install build dependencies

```bash
apt-get update
apt-get install -y \
    build-essential \
    libncurses-dev \
    bison flex \
    libssl-dev libelf-dev \
    bc dwarves rsync fakeroot \
    debhelper \
    linux-source-6.12 linux-config-6.12
```

`linux-source-6.12` provides the kernel source tarball. `linux-config-6.12` provides the Debian cloud kernel configuration as a starting point.
{% endstep %}

{% step %}

### Extract the kernel source

```bash
mkdir -p /opt/kernel-build
cd /opt/kernel-build
tar -xf /usr/src/linux-source-6.12.tar.xz --strip-components=1
```

The source tree unpacks to roughly 1.5 GB.
{% endstep %}

{% step %}

### Start from the Debian cloud config

```bash
cd /opt/kernel-build
xzcat /usr/src/linux-config-6.12/config.amd64_none_cloud-amd64.xz > .config
```

Debian's official `cloud-amd64` kernel configuration is already stripped of most desktop and legacy hardware drivers. Using this configuration provides a sensible, production-ready starting point rather than building a kernel configuration from scratch.
{% endstep %}

{% step %}

### Apply the hardening configuration

Create and run the hardening script:

```bash
cat > /opt/kernel-build/harden.sh << 'EOF'
#!/bin/bash
# Hardened kernel configuration for IONOS CLOUD VMs
# Validated: Debian 13 / kernel 6.12 / August 2026
set -e
cd /opt/kernel-build
SC="$(pwd)/scripts/config"

# ── Kernel replacement / debug ─────────────────────────────────────────────
$SC --disable CONFIG_KEXEC
$SC --disable CONFIG_KEXEC_FILE
$SC --disable CONFIG_CRASH_DUMP
$SC --disable CONFIG_CRASH_HOTPLUG
$SC --disable CONFIG_HIBERNATION
$SC --disable CONFIG_MAGIC_SYSRQ

# ── Console / display ──────────────────────────────────────────────────────
# Keep the VT framework alive (required for initramfs and systemd /dev/console)
$SC --enable  CONFIG_VT
$SC --enable  CONFIG_VT_CONSOLE    # needed: provides /dev/console for systemd startup
$SC --enable  CONFIG_VGA_CONSOLE   # needed: text-mode console backend, screen stays dark
$SC --disable CONFIG_FRAMEBUFFER_CONSOLE  # no framebuffer console → no resolution switch
# FB framework stays enabled (CONFIG_FB=y) — removing it causes other cascades

# ── Input subsystem: framework alive, all drivers removed ─────────────────
$SC --enable  CONFIG_INPUT         # keep framework
$SC --disable CONFIG_INPUT_KEYBOARD
$SC --disable CONFIG_INPUT_MOUSE
$SC --disable CONFIG_INPUT_TABLET
$SC --disable CONFIG_INPUT_TOUCHSCREEN
$SC --disable CONFIG_VIRTIO_INPUT
$SC --disable CONFIG_SERIO        # PS/2 bus — no keyboard/mouse
$SC --disable CONFIG_RMI4_CORE    # Synaptics RMI4 protocol stack — no touchpad/touchscreen bus

# ── HID: framework alive, all drivers removed ─────────────────────────────
$SC --enable  CONFIG_HID           # keep framework
$SC --disable CONFIG_HID_GENERIC
$SC --disable CONFIG_HID_RMI       # Synaptics RMI4 HID transport driver
$SC --disable CONFIG_HIDRAW
$SC --disable CONFIG_USB_HID

# ── USB: removed entirely ─────────────────────────────────────────────────
$SC --disable CONFIG_USB

# ── Graphics: DRM removed ─────────────────────────────────────────────────
$SC --disable CONFIG_DRM

# ── Host management channels ──────────────────────────────────────────────
$SC --disable CONFIG_SERIAL_8250        # no serial UART
$SC --disable CONFIG_VIRTIO_CONSOLE     # no virtio console channel
$SC --disable CONFIG_VSOCKETS          # no vsock

# ── Other attack surface ──────────────────────────────────────────────────
$SC --disable CONFIG_SOUND

# ── CVM: promote SEV_GUEST to built-in ───────────────────────────────────
# Debian ships SEV_GUEST=m (loadable module).
# If you later disable CONFIG_MODULES, this must be =y.
$SC --enable CONFIG_AMD_MEM_ENCRYPT
$SC --enable CONFIG_SEV_GUEST

# ── Required drivers: ensure they are present ─────────────────────────────
$SC --enable CONFIG_PCI
$SC --enable CONFIG_VIRTIO
$SC --enable CONFIG_VIRTIO_PCI
$SC --enable CONFIG_VIRTIO_BLK
$SC --enable CONFIG_VIRTIO_NET
# ACPI is required: IONOS API/DCD stop can send an ACPI power button event, but this is not guaranteed on every stop or reset.
# Without ACPI, the VM cannot be cleanly shut down without a hard reset.
$SC --enable CONFIG_ACPI

echo "Hardening overrides applied."
EOF
chmod +x /opt/kernel-build/harden.sh
bash /opt/kernel-build/harden.sh
```

Resolve any dependency changes automatically:

```bash
make olddefconfig
```

{% hint style="info" %}
**Note:** If the build fails with `unmet build dependencies: debhelper-compat (= 12)`, fix it using the following:

```bash
sed -i 's/debhelper-compat (= 12)/debhelper-compat (= 13)/' \
    /opt/kernel-build/scripts/package/mkdebian
```

{% endhint %}
{% endstep %}

{% step %}

### Verify the configuration

Before building, confirm key options are correct.

**Management channels removed:**

```bash
grep -E '^(# )?CONFIG_(SERIAL_8250|VIRTIO_CONSOLE|VSOCKETS|KEXEC|KEXEC_FILE|HIBERNATION|MAGIC_SYSRQ|USB)' \
    /opt/kernel-build/.config
```

Expected — all `# CONFIG_... is not set`:

```
# CONFIG_SERIAL_8250 is not set
# CONFIG_VIRTIO_CONSOLE is not set
# CONFIG_VSOCKETS is not set
# CONFIG_USB is not set
# CONFIG_KEXEC is not set
# CONFIG_KEXEC_FILE is not set
# CONFIG_HIBERNATION is not set
# CONFIG_MAGIC_SYSRQ is not set
```

**Input and HID drivers removed:**

```bash
grep -E '^(# )?CONFIG_(INPUT_KEYBOARD|INPUT_MOUSE[ =]|SERIO|HID_GENERIC|VIRTIO_INPUT)' \
    /opt/kernel-build/.config
```

Expected — all `# CONFIG_... is not set`.

**Console drivers — exactly as required:**

```bash
grep -E '^CONFIG_(VT|VT_CONSOLE|VGA_CONSOLE|FRAMEBUFFER_CONSOLE)=' \
    /opt/kernel-build/.config
```

Expected:

```
CONFIG_VT=y
CONFIG_VT_CONSOLE=y
CONFIG_VGA_CONSOLE=y
```

`FRAMEBUFFER_CONSOLE` is absent or `not set`.

**Required drivers and CVM support:**

```bash
grep -E '^CONFIG_(VIRTIO_BLK|VIRTIO_NET|VIRTIO_PCI|PCI|AMD_MEM_ENCRYPT|SEV_GUEST|ACPI)=' \
    /opt/kernel-build/.config
```

Expected — all `=y`.

{% hint style="info" %}
**Note:** Disabling `CONFIG_DEBUG_FS` against the default Debian cloud configuration will fail because the memory management subsystem selects it regularly. It does not present a meaningful attack surface without root access to mount or read the debug filesystem.
{% endhint %}
{% endstep %}

{% step %}

### Build the kernel

```bash
cd /opt/kernel-build
make -j$(nproc) bindeb-pkg LOCALVERSION=-hardened 2>&1 | tee /var/log/kernel-build.log
```

Build time is approximately 6 minutes on 16 vCPUs, or 20–30 minutes on 2–4 vCPUs. Upon completion, packages appear in the parent directory:

```bash
ls /opt/*.deb
# linux-image-[YOUR_KERNEL_VERSION]-hardened_*.deb
# linux-headers-[YOUR_KERNEL_VERSION]-hardened_*.deb
```

{% endstep %}

{% step %}

### Install

```bash
dpkg -i /opt/linux-image-[YOUR_KERNEL_VERSION]-hardened_*.deb
update-grub
```

{% endstep %}

{% step %}

### Configure startup hardening

Add a Grand Unified Bootloader (GRUB) configuration snippet in `/etc/default/grub.d/` to suppress residual console output while preserving a functional menu timeout during testing.

```bash
cat > /etc/default/grub.d/20_hardening.cfg << 'EOF'
# Suppress kernel log output — screen is dark at runtime
GRUB_CMDLINE_LINUX_DEFAULT="quiet loglevel=0"
# Keep menu visible during transition (override the default GRUB_TIMEOUT=0)
GRUB_TIMEOUT=10
GRUB_TIMEOUT_STYLE=menu
EOF
update-grub
```

Mask the `getty` services to prevent login prompts from appearing on the dark console:

```bash
systemctl mask getty.target serial-getty.target
```

{% hint style="info" %}
**Note:** <code class="expression">space.vars.ionos\_cloud</code> images ship with `/etc/default/grub.d/15_timeout.cfg` containing `GRUB_TIMEOUT=0`. The `20_hardening.cfg` file above overrides this during development. Reduce or eliminate the GRUB menu timeout once your build is validated for production.
{% endhint %}

{% hint style="info" %}
**Note:** <code class="expression">space.vars.ionos\_cloud</code> images may include `console=ttyS0` for serial access. With `CONFIG_SERIAL_8250=n` set in the hardened kernel, this argument is ignored. Leave it in place; it does no harm and allows stock recovery kernels started from GRUB to retain serial access.
{% endhint %}
{% endstep %}

{% step %}

### Restart and verify

Use `grub-reboot` to start the new kernel exactly once. It reverts to the previous default if something fails:

```bash
# Find the submenu entry ID:
grep 'menuentry_id_option.*hardened' /boot/grub/grub.cfg

# Boot it once (adjust the entry path to match your menu):
grub-reboot "Advanced options for Debian GNU/Linux>Debian GNU/Linux, with Linux [YOUR_KERNEL_VERSION]-hardened"
reboot
```

After the VM comes back (30–60 seconds), verify:

```bash
# Kernel version
uname -r
# Expected: [YOUR_KERNEL_VERSION]-hardened

# USB controllers: no driver
lspci -nnk | grep -A2 'USB'
# Expected: no "Kernel driver in use" line

# Network working
ping -c1 8.8.8.8

# Stop the VM from outside, to test ACPI handling:
# DCD: select Power > Stop and confirm, or API: POST /datacenters/{datacenterId}/servers/{serverId}/stop
# IONOS does not guarantee ACPI delivery on stop or reset — if the VM does not shut down cleanly, the platform force-stops it
```

Once confirmed, set the hardened kernel as the permanent default:

```bash
grub-set-default \
  "Advanced options for Debian GNU/Linux>Debian GNU/Linux, with Linux [YOUR_KERNEL_VERSION]-hardened"
```

{% endstep %}

{% step %}

### Verify the DCD console behaviour

Open the DCD console for the VM during a restart to confirm the expected output:

1. **GRUB Menu (Visible):** GRUB has its own display support, independent of the Linux kernel.
2. **Loading Linux or initial ramdisk:** GRUB is loading the kernel.
3. **`EFIstub: Loaded initrd from...`:** Kernel EFI stub confirms initrd handoff.
4. **Black screen:** Kernel starts. Video Graphics Array (VGA) text-mode console is registered but produces no output; `loglevel=0` suppresses everything else.

The screen stays black for the remainder of the VM's life. The VM runs normally, SSH works, and the network is up, but the console is silent and non-interactive.

{% hint style="info" %}
**Note:** This step applies to a standard <code class="expression">space.vars.ionos\_cloud</code> VM used for testing. For Confidential VMs, remote console access is not currently available from the DCD.
{% endhint %}

{% hint style="info" %}
**Note:** To diagnose a startup failure, temporarily remove `quiet loglevel=0` from the kernel command line by editing the GRUB menu entry with `e` at startup. This exposes complete kernel startup messages on the DCD console. Restore the parameters once troubleshooting is complete.
{% endhint %}
{% endstep %}
{% endstepper %}

## Optional: Remove kernel modules

For maximum assurance on a production CVM, disable loadable kernel modules:

```bash
scripts/config --disable CONFIG_MODULES
```

Add this to `harden.sh` before `make olddefconfig`.

**Implications:**

* Every required driver must be compiled into the kernel (`=y`). For a CVM in general, `CONFIG_SEV_GUEST` works as either `=y` or `=m` — the kernel only fails to function as a CVM if it's disabled entirely. Once loadable modules are disabled (`CONFIG_MODULES=n`, this section), it must be `=y`, since a `=m` driver could never load. This guide uses `=y` throughout for that reason.
* `modprobe`, `insmod`, and `lsmod` are non-functional because runtime driver loading is disabled in the kernel.
* The full driver set is determined at build time and fixed. The attestation measurement proves that the kernel actually running is the exact kernel you built and supplied in the artifacts partition — not a substituted or modified one. Without `CONFIG_MODULES=n`, this identity proof only covers the kernel binary itself: modules loaded later from the (unmeasured) root file system are outside what the measurement verifies, so the built-in driver set it attests to would not be the complete picture. With `CONFIG_MODULES=n`, confirming the kernel's identity confirms its entire driver set, because nothing can be added afterward.

After adding `CONFIG_MODULES=n`, always perform a test restart before deploying to production.

{% hint style="info" %}
**Alternative: firmware-verified signed modules.** Disabling `CONFIG_MODULES=n` is not the only way to close the unmeasured-module gap. Depending on your build process, your environment, and your CVM firmware, you can instead supply firmware that cryptographically verifies your kernel's signature before allowing it to run. The Linux kernel also supports the use of signed modules. Your approach to ensure code integrity can go far beyond this how-to.
{% endhint %}

## Optional: Remove hypervisor capability

A CVM operates as a guest running inside a hypervisor; it does not require nested virtualization capabilities. Removing KVM support from the CVM kernel eliminates this capability at compile time. Even if the CVM is compromised, an attacker cannot instantiate nested VMs. With [`CONFIG_MODULES=n`](#optional-remove-kernel-modules) also in place, this becomes a cryptographically verifiable guarantee: an attestation verifier checking the kernel measurement can confirm KVM is absent from the running kernel, not just from the image you built. Without `CONFIG_MODULES=n`, the measurement only covers the kernel binary itself, so it cannot rule out a KVM-capable module loaded later from the unmeasured root file system.

{% hint style="info" %}
**Note:** AMD SEV-SNP does not support nested virtualisation. Removing KVM from a CVM kernel is therefore not just a security choice. It correctly reflects what the platform supports.
{% endhint %}

Add to `harden.sh`:

```bash
# ── CVM: remove hypervisor capability ──────────────────────────────────────
# A CVM is a guest; it must not act as a hypervisor host.
# Nested virtualisation is also unsupported by AMD SEV-SNP.
$SC --disable CONFIG_KVM
$SC --disable CONFIG_KVM_AMD
$SC --disable CONFIG_KVM_INTEL
```

Retain the guest-side features that improve the performance of the guest on a hypervisor:

| **Option**                | **Action** | **Reason**                                                                             |
| ------------------------- | ---------- | -------------------------------------------------------------------------------------- |
| `CONFIG_KVM`              | Remove     | Hypervisor host capability                                                             |
| `CONFIG_KVM_AMD`          | Remove     | AMD-specific host capability                                                           |
| `CONFIG_KVM_INTEL`        | Remove     | Intel-specific host capability (irrelevant on AMD; remove)                             |
| `CONFIG_HYPERVISOR_GUEST` | Keep       | Tells the kernel it is running inside a hypervisor and enables paravirt optimisations. |
| `CONFIG_KVM_GUEST`        | Keep       | Guest-side paravirt clock and KVM guest hooks; improves performance.                   |
| `CONFIG_VIRT_DRIVERS`     | Keep       | Required by `CONFIG_SEV_GUEST`. Do not disable.                                        |

After `make olddefconfig`, verify:

```bash
grep -E '^CONFIG_KVM[^_]|^CONFIG_KVM_AMD|^CONFIG_KVM_INTEL' /opt/kernel-build/.config
# Expected: all absent or "# CONFIG_KVM... is not set"

grep -E '^CONFIG_(HYPERVISOR_GUEST|KVM_GUEST|VIRT_DRIVERS|SEV_GUEST)=' /opt/kernel-build/.config
# Expected: all =y or =m
```

## Security properties and limitations

### What this achieves

| **Property**                            | **Mechanism**                                                                                                                                       |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| No serial console                       | `CONFIG_SERIAL_8250=n`. No Universal Asynchronous Receiver-Transmitter (UART) driver.                                                               |
| No virtio-console or vsock channel      | `CONFIG_VIRTIO_CONSOLE=n CONFIG_VSOCKETS=n`                                                                                                         |
| No USB interaction                      | `CONFIG_USB=n`. No USB stack.                                                                                                                       |
| No keyboard input                       | `CONFIG_INPUT_KEYBOARD=n CONFIG_SERIO=n`. No keyboard driver.                                                                                       |
| No Ctrl-Alt-Del                         | Keyboard input cannot reach the kernel.                                                                                                             |
| Screen dark at runtime                  | `VGA_CONSOLE` in text mode, `loglevel=0`; no framebuffer console.                                                                                   |
| No runtime kernel replacement           | `CONFIG_KEXEC=n`                                                                                                                                    |
| Shutdown without keyboard (best effort) | Advanced Configuration and Power Interface (ACPI) power button, if delivered by the API or DCD stop action — not guaranteed on every stop or reset. |

### What this does not protect against

* **Network traffic metadata** — the hypervisor observes packet sizes, timing, and IP addresses. SEV-SNP encrypts RAM but not network metadata. Use TLS or mTLS for all traffic.
* **Storage integrity** — the hypervisor controls what blocks virtual storage returns. Use LUKS2 dm-crypt with a key obtained using attestation, not stored on the image.
* **PCI device enumeration** — the guest kernel can still list PCI devices it has no driver for. This is harmless and expected.
* **AHCI SATA driver** — the emulated SATA controller has a driver bound in this configuration (`CONFIG_ATA` is not disabled). No SATA disk is attached in a standard <code class="expression">space.vars.ionos\_cloud</code> virtio VM, so it is inert in practice. For stricter builds, add `CONFIG_ATA=n` to `harden.sh`.
* **virtio-balloon, virtio-rng, and other Debian cloud-config defaults** — `harden.sh` above only disables the drivers listed in this guide (input, HID, USB, DRM, serial/console transports). It does not audit every driver the Debian cloud base config (`config.amd64_none_cloud-amd64`) enables by default. Check your own build's `.config` for additional bound `virtio-*` drivers beyond `virtio-blk`/`virtio-net`, and disable any you don't need.

### Attestation integration

For a CVM, the kernel command line is part of the cryptographic measurement that the platform takes when the VM starts. Place it in the mandatory `kernel-params.txt` file in the `/artifacts/` directory of the `LAUNCH_ARTIFACTS` partition — see [Prepare a Confidential VM image](/cloud/compute-services/compute-engine/confidential-computing-vm/api-how-tos/prepare-confidential-vm-image.md) for the full partition layout and file requirements:

```bash
echo "root=/dev/mapper/root ro quiet loglevel=0" > /artifacts/kernel-params.txt
```

`kernel-params.txt` must stay a single line and under 4 KB. The AMD Secure Processor includes its contents in this measurement alongside `firmware-code.fd`, `vmlinuz`, and `initrd.img` — no separate hashing step is needed. An altered command line produces a different measurement, and attestation fails.

## Troubleshooting

### VM does not come back after restart

1. The hardened kernel may have failed to start. If you have used `grub-reboot`, the previous kernel will start on the next attempt.
2. Before building, verify that `CONFIG_VIRTIO_NET=y` is in `.config`.
3. Run `update-initramfs -u` before restarting to ensure the initramfs includes `virtio-net`.

### SSH fails but VM is running

* Verify `CONFIG_VIRTIO_NET=y` and `CONFIG_VIRTIO_PCI=y`.
* Run `update-initramfs -u` on the installed kernel to ensure the initramfs includes the `virtio-net` module.

### Unexpected options re-enabled by `make olddefconfig`

After `harden.sh` and `olddefconfig`, re-run the verification greps. If an option comes back enabled, it has an unsatisfied dependency pulling it in. Add an explicit `$SC --disable` for that option to `harden.sh` and re-run.

### Unable to shut down the VM

Verify `CONFIG_ACPI=y`. The <code class="expression">space.vars.ionos\_cloud\_api</code> and DCD stop action can deliver an ACPI power button event, but this is not guaranteed on every stop; without ACPI support, the VM can only be hard-reset.

## Summary checklist

| **Error**                                                                  | **Config or action**                               |
| -------------------------------------------------------------------------- | -------------------------------------------------- |
| Base: Debian cloud-amd64 config                                            | `config.amd64_none_cloud-amd64.xz`                 |
| kexec / hibernation / SysRq disabled                                       | `CONFIG_KEXEC=n HIBERNATION=n MAGIC_SYSRQ=n`       |
| No serial UART                                                             | `CONFIG_SERIAL_8250=n`                             |
| No virtio-console or vsock                                                 | `CONFIG_VIRTIO_CONSOLE=n VSOCKETS=n`               |
| No USB                                                                     | `CONFIG_USB=n`                                     |
| No DRM                                                                     | `CONFIG_DRM=n`                                     |
| No keyboard / mouse / SERIO                                                | `INPUT_KEYBOARD=n INPUT_MOUSE=n SERIO=n`           |
| No HID drivers                                                             | `HID_GENERIC=n HIDRAW=n USB_HID=n`                 |
| No framebuffer console                                                     | `CONFIG_FRAMEBUFFER_CONSOLE=n`                     |
| VGA text console retained                                                  | `CONFIG_VGA_CONSOLE=y VT_CONSOLE=y`                |
| ACPI retained                                                              | `CONFIG_ACPI=y`                                    |
| virtio-blk / virtio-net retained                                           | `CONFIG_VIRTIO_BLK=y VIRTIO_NET=y`                 |
| SEV\_GUEST built-in                                                        | `CONFIG_SEV_GUEST=y`                               |
| Hypervisor capability removed (optional)                                   | `CONFIG_KVM=n CONFIG_KVM_AMD=n CONFIG_KVM_INTEL=n` |
| Built with `make bindeb-pkg`                                               |                                                    |
| `quiet loglevel=0` in GRUB cmdline                                         | `/etc/default/grub.d/20_hardening.cfg`             |
| getty services masked                                                      | `systemctl mask getty.target serial-getty.target`  |
| Start verified through SSH                                                 |                                                    |
| DCD console dark and non-interactive (standard VM test)                    |                                                    |
| ACPI power button handling verified (best effort, not guaranteed by IONOS) | DCD stop / API stop                                |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.ionos.com/cloud/compute-services/compute-engine/confidential-computing-vm/how-tos/hardened-cvm-kernel-howto.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
