You will turn one Linux server into a hypervisor that runs guest VMs, letting you build multi-node clusters on a single machine. By the end you will boot a nested guest and manage it entirely from the command line.

Learning Objectives

  • Enable nested virtualization in the KVM kernel module.
  • Provision a guest VM with libvirt and virt-install.
  • Manage VM lifecycle with virsh.
  • Time: ~3 hours · Difficulty: Advanced · Prereqs: a Linux host with CPU virtualization extensions and 8GB+ RAM.

Architecture Overview

Environment Setup

You will need: a Linux server with KVM (kvm-ok passes), libvirt, and virtinst installed. Note: kvm-ok ships in the cpu-checker package (sudo apt install -y cpu-checker), which the install command below does not include.

Before you begin: confirm CPU extensions with egrep -c '(vmx|svm)' /proc/cpuinfo (must be > 0).

Prerequisites

  • A Linux host (or VM with nesting allowed) with virtualization extensions and internet access.
  • Debian/Ubuntu for the apt commands shown (adapt package names for RHEL/Fedora).
  • At least 8 GB RAM and 40 GB disk free for host plus guests.
  • Root or sudo access; libvirt group membership for non-root management.

Step-by-Step Execution

01
Install the virtualization stack

These packages provide the KVM hypervisor, the libvirt management API, and the virt-install provisioning tool.

sudo apt install -y qemu-kvm libvirt-daemon-system virtinst
[ROOT REQUIRED] Installs KVM, libvirtd, and virt-install.

Expected output:

Setting up qemu-kvm (1:8.2.2) ...
Setting up libvirt-daemon-system ...
Processing triggers for man-db ...

What this teaches: qemu-kvm is the hypervisor, libvirt-daemon-system the management API, virtinst the provisioner; installing the trio gives both CLI and API control.

02
Enable nested virtualization

Nesting exposes virtualization extensions to guests so an L1 VM can itself run VMs.

echo 'options kvm_intel nested=1' | sudo tee /etc/modprobe.d/kvm.conf && sudo modprobe -r kvm_intel; sudo modprobe kvm_intel

Expected output:

(no output on success; verify with: cat /sys/module/kvm_intel/parameters/nested)
Y

What this teaches: options kvm_intel nested=1 persists the flag in modprobe.d so reboots keep nesting; reloading the module applies it without a reboot when no guests run. On AMD hosts the module is kvm_amd with the same nested=1 option. If modprobe -r fails because guests are running, skip the reload and reboot instead; the modprobe.d option takes effect at the next module load.

03
Provision a guest with virt-install

This downloads an Ubuntu cloud image, then builds a guest around it, scripted and repeatable.

sudo wget -q -O /var/lib/libvirt/images/node1.qcow2 https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
[ROOT REQUIRED] Fetches the Ubuntu 22.04 cloud image into libvirt's default storage pool.

Expected output:

(no output on success; verify with: qemu-img info /var/lib/libvirt/images/node1.qcow2 | head -3)

What this teaches: --import builds a guest around an existing disk image, so you must supply one first. Cloud images ship with cloud-init and grow-on-boot support, which is why they are the standard guest source for scripted labs.

sudo virt-install --name node1 --memory 2048 --vcpus 2 --disk /var/lib/libvirt/images/node1.qcow2,format=qcow2,bus=virtio --osinfo ubuntu22.04 --import --noautoconsole

Expected output:

Starting install...
Creating domain...  |    0 B  00:00:00
Domain creation completed.

What this teaches: --osinfo selects optimized virtual hardware for the guest OS (the older --osvariant flag still works as an alias); --import skips the installer and boots the existing image, making provisioning fully scripted. Warning: --import with --disk size=10 and no image path creates a blank disk the guest cannot boot from, which is the most common virt-install beginner error.

04
Manage the guest with virsh

Expected output:

$ virsh list --all
Id Name State ------------------------- 1 node1 running

What this teaches: virsh list --all is the inventory view; from here console, snapshot-create-as, shutdown, and undefine cover the whole lifecycle.

How It Works

KVM turns the Linux kernel itself into a hypervisor via /dev/kvm, while QEMU provides the device emulation and libvirt offers a stable management API above both. This layering is why one virsh works across KVM, Xen, and even VMware: scripts target libvirt, not hypervisor quirks.

Nested virtualization exposes the host's VMX/SVM extensions to the L1 guest, letting it run KVM in turn. The use case is lab density: one physical host (or one large cloud VM) becomes a whole virtual datacenter for testing clustering, Proxmox, or Kubernetes topologies that need multiple nodes.

virt-install generates libvirt XML from flags, which matters for reproducibility: the same command provisions the same guest every time, and the XML can be versioned. virsh then manages the result through the full domain lifecycle, from define to undefine.

Progress So Far

Testing & Validation

cat /sys/module/kvm_intel/parameters/nested && virsh domstate node1 && virsh net-list --all

You should see Y for nesting, running for the guest, and the default NAT network active (it provides DHCP to guests; start it with virsh net-start default if it is inactive). If all three pass, your nested hypervisor lab works.

Troubleshooting

SymptomLikely causeFix
No /dev/kvm deviceExtensions disabled or the kvm module not loadedEnable VT-x/AMD-V in firmware and modprobe kvm_intel (or kvm_amd)
Default network has no DHCPlibvirt's default NAT network not startedRun virsh net-start default and virsh net-autostart default
Guest console shows nothingNo console device configured on the domainAdd --graphics none --console pty,target_type=serial for text consoles
Permission denied on virshUser outside the libvirt groupusermod -aG libvirt $USER, then log out and back in
virt-install hangs at installWaiting on interactive installer inputUse --noautoconsole with cloud images, or provide automated install media
Nested L2 extremely slowDouble virtualization overheadAccept for labs; reduce vCPUs and use host-passthrough CPU model
Troubleshooting
  • nested reads N: reload the module after adding the modprobe option; persist via /etc/modprobe.d.
  • Guest has no virtualization: set the L1 CPU mode to host-passthrough.
  • libvirtd not running: sudo systemctl enable --now libvirtd.

Extension Ideas

  • Boot two guests and build the Kubernetes Basics lab across them.
  • Compare with the Proxmox Homelab approach.
  • Automate guest creation with cloud-init user-data.

Key Results

  • Enabled nested KVM and booted a guest inside a VM.
  • Provisioned guests reproducibly with one virt-install command.
  • Built multi-node topologies on a single physical host.
  • Managed full VM lifecycle through virsh.

Key Takeaways

  • KVM plus libvirt plus virt-install is the complete open-source stack.
  • Persist nested=1 in modprobe.d for reboot survival.
  • virt-install flags generate reproducible libvirt XML.
  • virsh covers the entire guest lifecycle from the CLI.
  • Start libvirt's default network or guests get no DHCP.

Practice Challenges

Challenge 1: Cluster in a box

Goal: provision three nested guests, give them static IPs, and get passwordless SSH between all pairs.

Hint: cloud-init NoCloud seeds set users and keys.

Challenge 2: Snapshot policy

Goal: script nightly virsh snapshots of a guest with a 7-day rotation, logging each run.

Hint: snapshot-create-as with dated names plus a prune loop.