SSD Nodes Learn
Guides Matt ConnorBy Matt Connor · Updated 2026-07-15

Nested virtualization on a VPS: KVM & Proxmox

Check whether your VPS exposes VT-x or AMD-V, enable nested KVM, and run Proxmox or Windows in a guest hypervisor. Includes the errors you will actually hit.

The short answer

Nested virtualization is a hypervisor running inside a virtual machine: your VPS is already a guest, and you want it to host guests of its own. It works only when your provider's hypervisor deliberately exposes the CPU's virtualization extensions to your instance — check /proc/cpuinfo for the vmx flag (Intel) or svm (AMD), and if neither appears, nothing you configure inside the VPS will fix it.

One expectation-setter first: Docker does not need any of this. Containers share your VPS kernel and never touch /dev/kvm. If the real goal is "run several services in containers on my server", you already have what you need. Nesting matters when you want a second kernel — a Proxmox lab, a Windows guest, Firecracker microVMs, an Android emulator, a Kubernetes testbed of real VMs, or CI runners that boot VM images.

What is actually being nested

Three layers:

  • L0 — the provider's hypervisor, on the metal. You have no access to it.
  • L1 — your VPS. To L0 this is just a guest.
  • L2 — the VM you want to run inside your VPS.

Hardware virtualization is VT-x (the vmx flag) plus EPT on Intel, AMD-V / SVM (svm) plus RVI/NPT on AMD. A hypervisor uses those instructions to enter guest mode and to let the CPU walk two page tables at once.

Neither was designed to be re-entrant, so nesting is emulated: when L1 executes a VMX instruction it traps to L0, which maintains the shadow structures for L2 on L1's behalf. KVM does this well, but it is L0 doing extra work on every exit — which is why the provider has to opt in.

Two conditions must both hold for an accelerated L2:

  1. L0's KVM module is loaded with nested=1.
  2. L0 gives your VPS a CPU model that carries the flag — <cpu mode='host-passthrough'/> in libvirt, cpu: host in Proxmox, -cpu host in raw QEMU. A generic emulated model (qemu64, kvm64) hides vmx even when nesting is on globally.

Check your VPS in one minute

# 1. Are you in a VM, and under what?
systemd-detect-virt          # kvm, vmware, xen, microsoft, or "none" on metal

# 2. Does the CPU expose the extensions to you?
grep -o -E 'vmx|svm' /proc/cpuinfo | sort -u
lscpu | grep -i -E 'virtual|hypervisor'

# 3. The definitive check
sudo apt update && sudo apt install -y cpu-checker
kvm-ok

# 4. The device node the whole stack depends on
ls -l /dev/kvm

A usable instance prints vmx or svm, kvm-ok says KVM acceleration can be used, and /dev/kvm exists as root:kvm mode 660. If the flag is there but the device node is not, load the module by hand and read the kernel log:

sudo modprobe kvm_intel     # or kvm_amd
sudo dmesg | tail -n 20

One file gets quoted constantly and is widely misread:

cat /sys/module/kvm_intel/parameters/nested   # Y or N

Inside your VPS that is the setting of your KVM module, and it governs whether an L2 guest could nest a third level. It says nothing about whether L0 enabled nesting for you — /proc/cpuinfo and kvm-ok answer that. The nested parameter is the knob you set on a machine you own outright:

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

Module removal is refused while a VM is running, so shut guests down first.

Why most VPS hosts leave it off

  • Live migration. Handing you vmx means exposing a host-passthrough CPU, and a guest that sees the exact host CPU model cannot be safely migrated to a machine with a different one. A host that drains nodes by migrating customers gives that up the moment it enables nesting.
  • Attack surface. The nested VMX/SVM paths are among the most intricate code in the kernel's virtualization layer, with a CVE history to match.
  • L0 may not be KVM. If systemd-detect-virt prints vmware, xen or microsoft, the nesting rules are that stack's, not KVM's.

No flag on your instance? Ask support (some enable it per-VM), pick a plan that documents nesting, or move to a dedicated box. The rest of this assumes root on a machine that shows the flag.

Running an L2 guest with libvirt

sudo apt install -y qemu-system-x86 libvirt-daemon-system virtinst ovmf
sudo systemctl enable --now libvirtd
sudo usermod -aG libvirt,kvm "$USER"   # log out and back in

virt-install \
  --name lab1 \
  --memory 2048 \
  --vcpus 2 \
  --cpu host-passthrough \
  --disk path=/var/lib/libvirt/images/lab1.qcow2,size=20,format=qcow2,bus=virtio \
  --network network=default,model=virtio \
  --os-variant debian13 \
  --location https://deb.debian.org/debian/dists/trixie/main/installer-amd64/ \
  --graphics none \
  --console pty,target_type=serial \
  --extra-args 'console=ttyS0,115200n8'

No graphical session is needed. A serial install runs for a while, so start it inside a persistent shell: the same tmux workflow that keeps Claude Code sessions alive on a VPS keeps a virt-install console attached across a dropped SSH connection. If --os-variant debian13 is rejected, your osinfo-db predates the release — run osinfo-query os and pick a name that exists. --cpu host-passthrough forwards vmx down into L2, needed only if L2 must virtualize in turn. Make the guest boot-safe with virsh autostart lab1.

The virtio bus on disk and NIC is not decoration: emulated IDE and e1000 devices trap into the hypervisor far more often than virtio queues do, and under nesting every trap is paid twice.

Networking: the part tutorials skip

Your VPS has one public IP and sits behind a fabric that filters unknown MAC addresses. Two consequences follow.

Bridging L2 guests onto the public network usually will not work. Put br0 on the public NIC, give the guest its own MAC, and you will watch ARP go out and nothing come back — the provider's switch drops frames from a MAC it never leased you. If that is your symptom, stop debugging the bridge; this is the mechanism.

Use the NAT network instead. libvirt ships default: virbr0, 192.168.122.0/24, dnsmasq leases, outbound working immediately. For inbound, terminate TLS on L1 and proxy in — the certificate paths below come from issuing a Let's Encrypt certificate with Certbot on Nginx:

server {
    listen 443 ssl;
    server_name lab.example.com;

    ssl_certificate     /etc/letsencrypt/live/lab.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/lab.example.com/privkey.pem;

    location / {
        proxy_pass http://192.168.122.50:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Give the guest a static lease first (virsh net-edit default) so the address in that proxy_pass stays put.

Management interfaces stay off the internet: VNC on 5900 and the Proxmox web UI on 8006 belong on loopback, reached over an SSH tunnel (ssh -N -L 8006:127.0.0.1:8006 you@your-vps) or across a self-hosted WireGuard VPN into the VPS, which puts the whole 192.168.122.0/24 guest range one private hop away. Keep the firewall narrow — sudo ufw allow 22,80,443/tcp, nothing else. If guests lose outbound connectivity right after you enable ufw, confirm net.ipv4.ip_forward is still 1 and pin it in /etc/sysctl.d/99-kvm.conf.

Proxmox on a VPS

Proxmox VE 9 is Debian 13 underneath, so it installs onto a Debian VPS by adding the pve-no-subscription repository and the proxmox-ve package. Take the repository and keyring lines from Proxmox's own current documentation — a URL copied from an old blog post breaks the install.

The packages are not the hard part. Proxmox expects vmbr0 bridged to a physical NIC, which walks straight into the MAC-filtering dead end above. The shape that works on a VPS is a NAT'd or routed vmbr0 with no physical port attached, guests on a private range, and DNAT rules or a reverse proxy on the host for anything public. Where the public-facing services are containers rather than VMs, Traefik fronting multiple apps from one Docker Compose file covers the same routing job with automatic certificates. Snapshot /etc/network/interfaces first: a bad bridge definition locks you out of a machine whose console you may not have.

Performance, stated honestly

Nested is slower than single-level, and the mechanism is specific rather than diffuse: memory access is not where the cost lands, exits are. With EPT/NPT present, L0 maintains shadow page tables for L2 and ordinary memory reads run at hardware speed. What gets expensive is every operation that leaves guest mode — I/O, timer interrupts, MMIO, inter-processor interrupts — since an L2 exit is handled by L0 and may be reflected back through L1. CPU-bound work over data already in RAM looks close to native; anything dominated by syscalls, packets and disk I/O feels the layers.

So: virtio devices everywhere. And your qcow2 file lives on a disk the provider has already virtualized — two thin-provisioning layers stacked, where cache=none on the guest disk stops the same blocks sitting in two page caches at once. No benchmark numbers here: measure your own workload on your own instance.

Failure modes, and the strings you will see

INFO: /dev/kvm does not exist / KVM acceleration can NOT be used from kvm-ok. Either the module is not loaded, or the flag is not exposed. Check /proc/cpuinfo first.

kvm: disabled by bios in dmesg. On bare metal, flip the VT-x/SVM toggle in firmware. Inside a VPS it means L0 is not handing you the extensions, and nothing you type in the guest changes that.

modprobe: ERROR: could not insert 'kvm_intel': Operation not supported. The CPU your kernel sees has no vmx — again, an L0 decision.

Could not access KVM kernel module: Permission denied. Permissions, not hardware. ls -l /dev/kvm should show group kvm, mode 660; add yourself to that group and start a fresh login shell, since group membership does not apply to an already-running session.

kvm: Device or resource busy when QEMU starts. Another hypervisor module holds the CPU: run lsmod, look for vboxdrv or VMware modules alongside kvm_intel, and unload the one you do not want.

/var/run/libvirt/libvirt-sock: No such file or directory from virsh. The daemon is down: sudo systemctl enable --now libvirtd.

Proxmox: KVM virtualisation configured, but not available. A guest has KVM acceleration ticked on a host that cannot provide it. Fix the nesting, or untick it and accept emulation.

Android emulator: x86_64 emulation currently requires hardware acceleration! /dev/kvm again — usually the group case.

No error at all, and everything is glacial. QEMU with no accelerator flag falls back to TCG, its software emulator. It is correct and it is slow — a boot measured in seconds becomes one measured in minutes. Pass -accel kvm explicitly, so QEMU stops with an error instead of quietly emulating.

A guest vanishes mid-run. Look in dmesg for Out of memory: Killed process ... qemu-system-x86_64. An L2 guest is a process on L1, and the OOM killer treats it like any other. L2 RAM comes out of L1's fixed allocation — no borrowing from the host.

Operating it: backups, upgrades, limits

Backups. Copying a running guest's qcow2 gives you a corrupt image. Either virsh shutdown lab1 and copy, or take an external snapshot (virsh snapshot-create-as lab1 snap1 --disk-only --atomic) so writes divert to an overlay while you copy the now-static base, then fold it back with virsh blockcommit. Ship the copies off the VPS — a snapshot on the same disk protects against nothing.

Upgrades. apt full-upgrade installs new kvm_intel/kvm_amd modules, but the running kernel keeps the old ones until you reboot. Keep the previous kernel installed and re-run kvm-ok after every kernel change: a host that comes back without vmx is then one boot entry away from working again.

Where this stops scaling. One public IP means every L2 service reaches the world through a proxy or a DNAT rule on L1. Live migration is not on the menu. Under CPU contention the nested exit path is the first thing to feel it. And a hypervisor with several guests is a machine whose RAM you have already spent — nested VMs cannot overcommit their way out of a fixed allocation. When a lab outgrows that, the answer is not a taller nested stack; it is a dedicated box where you are L0 and none of this applies.

FAQ

Do I need nested virtualization to run Docker on a VPS?

No. Containers share your VPS kernel and never open /dev/kvm, so a plain instance with no vmx or svm flag runs Docker and Docker Compose fine. Nesting only matters when you want a second kernel: a Proxmox lab, a Windows guest, Firecracker microVMs, an Android emulator, or CI runners that boot VM images.

How do I check whether my VPS supports nested virtualization?

Run grep -o -E 'vmx|svm' /proc/cpuinfo | sort -u, then kvm-ok from the cpu-checker package. A usable instance prints vmx (Intel) or svm (AMD), kvm-ok reports KVM acceleration can be used, and /dev/kvm exists with group kvm and mode 660. Ignore /sys/module/kvm_intel/parameters/nested for this question — that file describes your own KVM module, not what the provider's hypervisor exposed to you.

Why do most VPS providers disable nested virtualization?

Exposing vmx requires a host-passthrough CPU model, and a guest that sees an exact host CPU cannot be live-migrated onto a machine with a different one — a provider that drains nodes by moving customers around gives that up. The nested VMX/SVM code paths also carry a long CVE history. Some hosts still enable it per-VM on request, and others document nesting as a plan feature.

My nested VM has no network on the public bridge. What is wrong?

The provider's switch drops frames from a MAC address it never leased you, so an L2 guest bridged onto the public NIC sends ARP and hears nothing back. Stop debugging br0 — use libvirt's NAT default network (virbr0, 192.168.122.0/24), give the guest a static lease, and publish anything public through a reverse proxy or DNAT rule on the VPS itself.

How much slower is a nested VM?

The cost lands on VM exits, not on memory access. With EPT/NPT active, ordinary reads and writes inside L2 run at hardware speed, while I/O, timer interrupts, MMIO and IPIs are handled by L0 and may be bounced back through L1. CPU-bound work over data already in RAM looks close to native; syscall-, packet- and disk-heavy workloads feel every layer. Use virtio devices everywhere and cache=none on guest disks, then measure your own workload.

#nested-virtualization#kvm#proxmox#vps#qemu#libvirt