Nested Virtualization

Nested virtualization runs a virtual machine inside a machine that is already virtualized. It needs the host to expose hardware virtualization to the guest.

Nested virtualization is running a virtual machine inside a machine that is already virtualized. It works only when the host exposes the CPU's hardware virtualization extensions to its guest, because the guest kernel needs those extensions to act as a hypervisor for a second layer of virtual machines.

The term shows up in GitHub Actions whenever a job wants to boot something heavier than a container: an Android emulator, a QEMU guest, a libvirt domain, or a virtual machine image under test. Every runner is already a guest of some hypervisor, so the second layer exists only if the first layer permits it.

Definition

Hardware virtualization on x86 is a set of CPU instructions, published by Intel as VT-x and by AMD as AMD-V, that lets a hypervisor run guest code directly on the processor and trap the privileged operations it cares about. Arm publishes an equivalent set of virtualization extensions. Without those instructions a hypervisor has to interpret or rewrite guest instructions in software, which is correct and slow.

Nested virtualization is the case where the guest itself wants those instructions. Kernel documentation names the layers L0 for the bare metal host and its hypervisor, L1 for the guest that hypervisor runs, and L2 for the guest that L1 runs (KVM nested VMX documentation, checked on 2026-08-13). L0 keeps real control of the hardware and emulates the virtualization instructions on behalf of L1, so L1 believes it is talking to a bare processor.

LayerWhat it isWhat runs there
L0The physical machineThe cloud provider's hypervisor, with real access to VT-x, AMD-V, or the Arm extensions
L1The virtual machine you rent, such as a workflow runnerThe guest kernel, plus a hypervisor of its own when L0 exposes the extensions
L2The virtual machine the job startsAn Android emulator, a QEMU guest, or a virtual appliance under test

The decision belongs to L0. A guest cannot switch nested virtualization on from inside, because the instructions either appear in the virtual processor it was handed or they do not. Google Cloud states the same division in its own terms and requires the feature to be enabled on the instance or machine image before a guest can use it (Compute Engine nested virtualization overview, checked on 2026-08-13).

How the guest sees the result on Linux

On Linux the visible outcome is a character device at /dev/kvm. The kernel creates it when the KVM module loads, and KVM loads only when the processor the guest can see offers usable virtualization extensions. Three states are worth telling apart, because two of them produce a slow job rather than a failed one.

What the job findsCauseWhat happens next
No /dev/kvmThe outer hypervisor kept the virtualization extensions to itselfAny tool that wants hardware acceleration falls back to software emulation
/dev/kvm with crw-rw---- root:kvmExtensions exposed, and the job user is outside the kvm groupOpening the device is refused, and the same software fallback follows
/dev/kvm readable and writable by the job userExtensions exposed and permissions widened by a udev ruleThe second layer of virtual machines runs with hardware acceleration

Nested virtualization is a different mechanism from containers. A container shares the kernel of the machine it runs on and isolates processes with namespaces and cgroups, so it needs nothing from the CPU beyond ordinary user mode. A nested virtual machine brings its own kernel, its own virtual devices, and its own memory map, which is why the CPU has to help.

Example

An Android instrumentation suite is the common case in GitHub Actions. The emulator boots a full Android system image, and it runs at a usable speed only when it can hand the guest to the processor through /dev/kvm.

name: instrumentation-tests
on:
  push:
    branches: [main]

jobs:
  android-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 17
      - name: Report virtualization support
        run: |
          grep -c -E 'vmx|svm' /proc/cpuinfo || echo "no virtualization flags"
          ls -l /dev/kvm || echo "no /dev/kvm on this runner"
      - uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 34
          arch: x86_64
          script: ./gradlew connectedCheck

On a runner whose host withholds the extensions, the reporting step prints no /dev/kvm on this runner and the workflow keeps going. The emulator action probes the device before launch, logs ProbeKVM: This user doesn't have permissions to use KVM (/dev/kvm)., logs Disabling Linux hardware acceleration., and starts the emulator with -accel off (android-emulator-runner README, checked on 2026-08-13). The suite still passes, on a timescale that makes people blame the tests.

The device existing is half the requirement. Default permissions on /dev/kvm are crw-rw---- root:kvm, which leaves an ordinary job user unable to open it, so a udev rule has to widen them first:

      - name: Enable KVM group perms
        run: |
          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
            | sudo tee /etc/udev/rules.d/99-kvm4all.rules
          sudo udevadm control --reload-rules
          sudo udevadm trigger --name-match=kvm

That step is a property of the device permissions rather than of any one runner fleet, so it belongs in the workflow wherever the emulator runs. GitHub announced hardware accelerated Android virtualization for a subset of its own hosted runner classes and pointed at the same permission step (GitHub changelog, 23 February 2023, checked on 2026-08-13), which is a reminder that support varies by runner class rather than by operating system.

Two habits keep this cheap to diagnose. Print ls -l /dev/kvm early in any job that expects nested virtualization, and fail the job when the device is absent instead of letting the run continue, so a change in runner class shows up as a red build rather than as a duration that creeps up over a quarter.

FAQ

What is nested virtualization?

Nested virtualization is running a virtual machine inside a machine that is already a virtual machine. The outer hypervisor has to expose the host CPU's hardware virtualization extensions to its guest, so that the guest kernel can act as a hypervisor for a second layer of virtual machines.

How do I tell whether a GitHub Actions runner supports nested virtualization?

Run `ls -l /dev/kvm` in a step on a Linux runner. A missing device means the outer hypervisor kept the virtualization extensions to itself. A device that exists with `crw-rw---- root:kvm` permissions means the extensions are exposed and the job user still needs group access before it can open the device.

Why does an Android emulator job get slow instead of failing?

The android-emulator-runner action probes /dev/kvm before it launches. When the probe fails it logs a ProbeKVM line, disables Linux hardware acceleration, and starts the emulator with `-accel off`, so the job keeps running under software emulation and finishes much later than usual.

Start with $10 in free credits

Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.