Run Windows 11 on Linux with QEmu Nested Virtualization

Summary

How to set up a QEmu/KVM virtual machine on Linux with nested virtualization support for running Windows 11 and WSL2, including VirtIO drivers, HyperV configuration, and disk management.

I exclusively run Linux at home, and needed a Windows VM for building the virtual pinball suite and other software that required me to use the Windows Subsystem for Linux (WSL2).

Virtualbox vs QEmu

I initially chose Virtualbox for simplicity but never managed to install WSL2 on it. It appears that WSL2 is implemented on top of Windows HyperV technology, and it thus requires nested virtualization features to be used inside a VM.

Unlike what is announced in official documentation wsl-virtualbox, it seems nested virtualization support for HyperV did not make it to the latest 7.0 Virtualbox release (7.0.14-Debian as of this writing), and thus it was not possible to install WSL2 vbox-hyperv. This might change with the release of 7.1.

1
2
3
WslRegisterDistribution failed with error: 0x80370102
Please enable the Virtual Machine Platform Windows feature and ensure virtualization is enabled in the BIOS.
For information please visit https://aka.ms/enablevirtualization

This is the opportunity to give a try at QEmu which supports it and promises to be more efficient with the use of KVM and virtio.

QEmu setup

I followed the excellent writeup by raphtlw, for which the steps are equivalent for Windows 11 (beyond a couple of remaining IDE occurrences that should be SATA). The most important steps are summarized here and you can refer to the original page for a detailed explanation with screenshots.

Dependencies

1
sudo apt-get install qemu-kvm bridge-utils virt-manager qemu-system virt-viewer spice-vdagent

Regular setup

Before starting the installation process, you will need to download two ISOs:

  • Windows 11, available on the Microsoft website
  • The VirtIO drivers ISO, needed to set up both VirtIO devices during installation (network is mandatory) — mount it as a second CDROM drive.

Create a new VM that will be installed from an ISO file:

  • CPU, memory: I left the default settings.
  • Disk size: at least 60 GB is recommended — Windows itself takes around 30 GB, and build tools require an additional 10–20 GB. You can always extend the partition after installation.
  • Create a second CDROM drive in the Storage menu (select the medium type as CDROM) and point to the VirtIO ISO.
  • Enable boot from the SATA CDROM in Boot options. You can also select it via the BIOS menu if the machine does not boot.
  • Switch to VirtIO driver for both the HDD and the NIC for improved performance.

Fine-tuning HyperV support

The setup worked fine until I installed WSL2 and rebooted, at which point I was greeted by the infamous blue screen with the message SYSTEM_THREAD_EXCEPTION_NOT_HANDLED. The VM never managed to recover.

I found directions in this excellent writeup from Redpill Linpro, and after a couple of tries with the XML editor in the “Processor” section of the VM configuration, I managed to boot Windows again with these settings:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
  <features>
    <acpi/>
    <apic/>
    <pae/>
    <hyperv mode="custom">
      <vpindex state="on"/>
      <synic state="on"/>
    </hyperv>
    <smm state="on"/>
  </features>
  <cpu mode="custom" match="exact" check="partial">
    <model fallback="allow">Broadwell-noTSX-IBRS</model>
    <feature policy="disable" name="hypervisor"/>
    <feature policy="require" name="vmx"/>
  </cpu>
  <clock offset="utc">
    <timer name="rtc" tickpolicy="catchup"/>
    <timer name="pit" tickpolicy="delay"/>
    <timer name="hpet" present="no"/>
    <timer name="hypervclock" present="yes"/>
    <timer name="kvmclock" present="yes"/>
  </clock>

I have not yet tried to optimize these settings beyond confirming that disabling CPU passthrough and selecting the correct CPU architecture were part of the solution. The next step would be to dig into the documentation or continue the trial-and-error process to find which properties are actually required, or lead to the best performance. As the article says, your results may vary.

As a reference, here is the original configuration proposed by QEmu:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
  <features>
    <acpi/>
    <apic/>
    <hyperv>
      <relaxed state="on"/>
      <vapic state="on"/>
      <spinlocks state="on" retries="8191"/>
    </hyperv>
    <vmport state="off"/>
  </features>
  <cpu mode="host-passthrough"/>
  <clock offset="localtime">
    <timer name="rtc" tickpolicy="catchup"/>
    <timer name="pit" tickpolicy="delay"/>
    <timer name="hpet" present="no"/>
    <timer name="hypervclock" present="yes"/>
  </clock>

Windows 11 installation

Let the installer guide you until you reach the step where you choose on which disk to install Windows. The list should be empty.

Select the “Load drivers” option, and browse the second CDROM (E:) to find the correct drivers for your OS and platform (note that sometimes there is no win11 version but win10 also works):

  • E:\viostor\w11\amd64 (disk)
  • E:\NetKVM\w11\amd64 (NIC)
  • E:\qxldod\w10\amd64 (graphics)

Check “Hide drivers that are not compatible with this computer’s hardware” to ensure the correct drivers are found. If nothing shows up, you may have forgotten to switch a device to VirtIO.

Further down in the installation process, you will not have the opportunity to load new drivers through the GUI. You might reach a step where the installer tries to find a network connection and gets stuck because there is no network interface. In that case, hit Shift+F10 to open a shell, navigate to the respective folders, and run:

1
2
pnputil /add-driver *.inf
pnputil /scan-devices

It happened to me that the installer found the network interface but refused to connect because of no internet connectivity. Running a ping from the shell confirmed the issue. On the host, packets arrived at the bridge but were not forwarded, likely because of missing iptables rules. The simplest fix is to restart libvirtd — this will not affect running VMs:

1
sudo service libvirtd restart

Guest agent and additional drivers

Once Windows is installed, you should also install:

  • E:\virtio-win-guest-tools.exe from the VirtIO ISO
  • SPICE guest tools (under the Guest / Windows binaries section) for features such as clipboard sharing and dynamic resolution between the host and guest

You can freely download the installer online with the Community edition.

These are the workload settings I used for my current use case. They require approximately 10 additional GB.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Web & Cloud
    [ ] ASP.NET and web development
    [ ] Azure development
    [X] Python development, could be useful
    [ ] Node.js developmen
Desktop & Mobile
    [ ] .NET Multi-platform App UI development
    [ ] .NET Desktop development
    [X] Desktop development with C++
    [ ] Universal Windows Platform development
    [ ] Mobile development with C++
Gaming
    [X] Game development with Unity
    [X] Game development with C++
Other toolsets
    [ ] Data storage and processing
    [ ] Data science and analytical applications
    [ ] Visual Studio extension development
    [ ] Office / SharePoint development
    [X] Linux and embedded development with C++

Git will be required, and you can find Windows binaries from the official website.

WSL2

You can install WSL2 directly from the Microsoft store, or through the command line as shown below.

Open cmd.exe from the start menu and run as administrator:

1
wsl --install

If you have Windows 11, or a recent enough version of Windows 10, you should already have WSL2. Confirm with:

1
2
3
4
5
6
7
8
$ wsl -v
WSL version: 2.1.4.0
Kernel version: 5.15.146.1-2
WSLg version: 1.0.60
MSRDC version: 1.2.5105
Direct3D version: 1.611.1-81528511
DXCore version: 10.0.25131.1002-220531-1700.rs-onecore-base2-hyp
Windows version: 10.0.22631.2861

More information can be found in wslinfo, wsl1 and wsl2. The system will ask you to reboot.

You can then install a Linux distribution (e.g. Debian):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ wsl --install --distribution Debian

Installing Windows optional component: VirtualMachinePlatform

Deployment Image Servicing and Management tool
Version: 10.0.22621.2792

Image Version: 10.0.22631.2861

Enabling feature(s)
[==========================100.0%==========================]
The operation completed successfully.
The requested operation is successful. Changes will not be effective until the system is rebooted.
Installing: Debian GNU/Linux
Debian GNU/Linux has been installed.
The requested operation is successful. Changes will not be effective until the system is rebooted.

After choosing a login and password, you should have a working Linux shell.

Maintenance

If you run out of disk space, you can either try to reclaim some space or extend the Windows image file.

Reclaim space: Compact OS, virtual memory and hibernation files

You can first try to uninstall unused applications, although the setup described above has hardly any extra software installed.

You can reclaim a few GB by compressing system files using the “Compact OS” feature of Windows, following this tutorial:

  1. Open Start.
  2. Search for Command Prompt, right-click the result, and select Run as administrator.
  3. Check whether Compact OS is already enabled:
1
Compact.exe /CompactOS:query
  1. Enable Compact OS:
1
Compact.exe /CompactOS:always

The compression process may take up to 20 minutes. You can revert the change at any time with Compact.exe /CompactOS:never.

You can also save space by reducing the virtual memory and hibernation files. Open an administrator Command Prompt and run:

1
powercfg /h /type reduced

This reduces the hibernation file by about 30%. To remove it entirely:

1
powercfg /h /off

To restore the full hibernation file later:

1
powercfg /h /size 100

Extending the QEmu image size

While the VM is shut down, resize the image:

1
sudo qemu-img resize /var/lib/libvirt/images/win11.qcow2 +10G

You can then use Windows’ Disk Manager to extend the filesystem over the newly available space.

I could not move the recovery partition sitting between the C: drive and the unallocated space from within Windows. To work around this, you can use the host-side tools:

1
2
sudo modprobe nbd max_part=16
sudo qemu-nbd -c /dev/nbd0 /var/lib/libvirt/images/win11.qcow2

KDE Partition Manager did not initially see the additional 10 GB. GParted did, but raised a warning:

1
2
3
Not all of the space available to /dev/nbd0 appears to be used, you can fix
the GPT to use all of the space (an extra 20971520 blocks) or continue with
the current setting?

After repairing the GPT, both tools worked correctly:

1
sudo gparted /dev/nbd0

Once done, disconnect the image:

1
sudo qemu-nbd -d /dev/nbd0

References