This guide documents migrating a Windows 11 VM created with QuickEmu to libvirt/virt-manager, with nested virtualization support for running Hyper-V, WSL2, and nested VMs inside Windows.
Tested on Pop!_OS 24.04 with an Intel Core i9-9980HK (Coffee Lake-H), Dell XPS 7590 (2019).
Install quickemu and quickgui
sudo apt-add-repository ppa:flexiondotorg/quickemu
sudo apt update
sudo apt install quickemu quickgui qemu-system-modules-spiceInstall libvirt and virt-manager
sudo apt install qemu-kvm libvirt-daemon-system virt-manager passt ovmf swtpmmkdir ~/QuickEmu
cd ~/QuickEmu
quickget windows 11
# quickget download will likely fail with error "WARNING! Microsoft blocked the automated download request based on your IP address."
# go to https://www.microsoft.com/en-us/software-download/windows11 with the browser and download the ISO file, store to ~/QuickEmu/windows-11/windows-11.iso
# when start
[ -f windows-11/windows-11.iso ] && quickemu --vm windows-11.conf || echo "Did you download the windows 11 iso file manually from https://www.microsoft.com/en-us/software-download/windows11? Download it to $PWD/windows-11/windows-11.iso."# Check current state
cat /sys/module/kvm_intel/parameters/nested
# Enable persistently
sudo tee /etc/modprobe.d/kvm-nested.conf <<EOF
options kvm-intel nested=1
EOF
# Reload (no VMs running)
sudo modprobe -r kvm_intel
sudo modprobe kvm_intel
# Verify
cat /sys/module/kvm_intel/parameters/nested
# Should output: Y# Create directory
sudo mkdir -p /var/lib/libvirt/images/windows-11
# Copy disk image and virtio drivers ISO from QuickEmu directory
sudo cp ~/QuickEmu/windows-11/disk.qcow2 /var/lib/libvirt/images/windows-11/
sudo cp ~/QuickEmu/windows-11/virtio-win.iso /var/lib/libvirt/images/windows-11/
# Set ownership
sudo chown -R libvirt-qemu:kvm /var/lib/libvirt/images/windows-11/Download the libvirt XML configuration and define the VM:
sudo virsh define windows-11-wsl2.xmlIf your existing Windows install has BitLocker or other TPM-bound secrets, preserve the TPM state from your QuickEmu setup:
#!/bin/bash
set -euo pipefail
domuuid=$(sudo virsh domuuid windows-11)
sudo mkdir -p "/var/lib/libvirt/swtpm/${domuuid}/tpm2"
sudo cp ~/QuickEmu/windows-11/tpm2-00.permall "/var/lib/libvirt/swtpm/${domuuid}/tpm2/tpm2-00.permall"
sudo chown -R swtpm:swtpm "/var/lib/libvirt/swtpm/${domuuid}"
sudo chmod 700 "/var/lib/libvirt/swtpm/${domuuid}"
sudo chmod 600 "/var/lib/libvirt/swtpm/${domuuid}/tpm2/tpm2-00.permall"
echo "TPM state restored for VM ${domuuid}"sudo virsh start windows-11Or open virt-manager and double-click the VM. You can open the console in virt-manager.
Open an elevated PowerShell and enable the required features:
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All -NoRestart
Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -NoRestart
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -NoRestart
Restart-ComputerAfter reboot, set the classic scheduler (required for nested KVM environments):
bcdedit /set hypervisorschedulertype classic
Restart-Computer# Check if the hypervisor is running (should return True)
(Get-CimInstance Win32_ComputerSystem).HypervisorPresent
# Check all features are enabled
Get-WindowsOptionalFeature -Online | Where-Object {$_.State -eq 'Enabled'} |
Where-Object {$_.FeatureName -match 'Hyper|Virtual|WSL|Subsystem'} |
Format-Table FeatureName, State
# Check Hyper-V services are running
Get-Service vmcompute, vmms | Format-Table Name, Status
# Check boot configuration
bcdedit /enum "{current}" | findstr hypervisorwsl --update --web-download
wsl --set-default-version 2
wsl --install UbuntuOpen an elevated PowerShell and install the OpenSSH Server feature:
# Install the OpenSSH Server capability
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*' | Add-WindowsCapability -Online
# Start the SSH service
Start-Service sshd
# Set it to start automatically on boot
Set-Service -Name sshd -StartupType Automatic
# Verify it's running
Get-Service sshd
# Allow SSH through Windows Firewall
New-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -DisplayName "OpenSSH Server (sshd)" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22When the Windows user (e.g. quickemu) is a member of the Administrators group, Windows OpenSSH ignores ~/.ssh/authorized_keys and instead reads from C:\ProgramData\ssh\administrators_authorized_keys.
Set your public key in the correct file:
$authorizedKeysFile = "C:\ProgramData\ssh\administrators_authorized_keys"
$publicKey = "ssh-ed25519 AAAA... your-key-comment"
# Make the file writable before modifying
icacls $authorizedKeysFile /grant "BUILTIN\Administrators:(F)"
# Set a key (overwrites existing content)
Set-Content -Path $authorizedKeysFile -Value $publicKey -Encoding UTF8
# Or append a key to an existing file
Add-Content -Path $authorizedKeysFile -Value $publicKey -Encoding UTF8
# Lock down permissions again (required for sshd to use the file)
icacls $authorizedKeysFile /inheritance:r /grant "SYSTEM:(R)" /grant "BUILTIN\Administrators:(R)"The administrators_authorized_keys file must be owned by SYSTEM and Administrators, with no inherited permissions. If permissions are wrong, sshd silently ignores the file.
icacls "C:\ProgramData\ssh\administrators_authorized_keys"Since the VM uses passt networking with port forwarding (port 22220 → 22):
ssh -p 22220 quickemu@localhostUses a named CPU model (Skylake-Client-IBRS) instead of host-passthrough. This is the key setting that makes nested Hyper-V work — host-passthrough causes Windows to crash when Hyper-V is enabled, while a named CPU model avoids the conflict and still exposes VMX for nested virtualization.
<cpu mode='custom' match='exact' check='partial'>
<model fallback='allow'>Skylake-Client-IBRS</model>
<topology sockets='1' dies='1' cores='4' threads='2'/>
<feature policy='require' name='vmx'/>
<feature policy='disable' name='mpx'/>
</cpu>vmx— exposes Intel VT-x to the guest for nested virtualizationmpxdisabled — Memory Protection Extensions removed, known to cause issues with nested Hyper-V
Adjust the CPU model to match your host. Check available models with:
virsh cpu-models x86_64Note: hiding KVM from the guest (<kvm><hidden state='on'/>) and disabling the hypervisor CPUID bit are not necessary — the named CPU model is what resolves the conflict with nested Hyper-V.
<hyperv mode='passthrough'>Passes through all compatible Hyper-V enlightenments. The following features were explicitly excluded because they require APICv/posted interrupts which may not be available on all CPUs:
evmcs— Enlightened VMCS, requires APICvemsr-bitmap— requires APICvreenlightenment— only needed for live migration
evmcs would improve performance of nested virtualization if it's available. It's not available for mobile CPUs such as Intel Core i9-9980HK.
For desktop / server CPUs, it's most likely available.
Static memory allocation with balloon disabled. Memory ballooning conflicts with Hyper-V's memory management:
<memory unit='GiB'>10</memory>
<currentMemory unit='GiB'>10</currentMemory>
<!-- ... -->
<memballoon model='none'/>Uses passt backend for user-mode networking with port forwarding. IPv6 is disabled to avoid errors on hosts with IPv6 turned off. SSH into the guest:
ssh -p 22220 localhostThe classic scheduler must be used instead of the default core scheduler. The core scheduler requires accurate SMT topology information which may not be properly exposed in a nested environment:
bcdedit /set hypervisorschedulertype classicCheck which OVMF files are available:
ls /usr/share/OVMF/Update the <loader> path in the XML to match. Common variants:
/usr/share/OVMF/OVMF_CODE_4M.secboot.fd/usr/share/OVMF/OVMF_CODE.secboot.fd/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd
Or let libvirt auto-select by removing the <loader> line entirely.
sudo apt install passtIf port forwarding uses a privileged port (below 1024), change to a high port. If IPv6 is disabled on the host, add <options ipv6='no'/> to the passt backend.
Reset NVRAM and redefine:
sudo virsh destroy windows-11
sudo virsh undefine windows-11 --nvram
sudo virsh define windows-11-wsl2.xml
sudo virsh start windows-11The Hyper-V hypervisor isn't launching. Check event logs:
Get-WinEvent -LogName "Microsoft-Windows-Hyper-V-Hypervisor-Admin" -MaxEvents 20 |
Format-Table TimeCreated, LevelDisplayName, Message -WrapVerify boot configuration:
bcdedit /enum "{current}" | findstr hypervisorhypervisorlaunchtype should be Auto and hypervisorschedulertype should be Classic.
# Backup NVRAM
sudo cp /var/lib/libvirt/qemu/nvram/windows-11_VARS.fd \
/var/lib/libvirt/qemu/nvram/windows-11_VARS.fd.bak
# Backup TPM
domuuid=$(sudo virsh domuuid windows-11)
sudo cp -a "/var/lib/libvirt/swtpm/${domuuid}" \
"/var/lib/libvirt/swtpm/${domuuid}.bak"
VM was created with QuickEmu and then moved to libvirt / virt-manager.