Host: CachyOS (Arch)
iGPU: Ryzen 7600x
Guest: Windows 11 Pro (Debloated via autounattend)
This repository documents the setting up and configuration for a Windows 11 VM running on my CachyOS PC. I need a quick way to launch Win11 for work purposes - taking teams/zoom calls, super heavy excel/powerpoint slides stuffs and when Im done, back to daily-driving my CachyOS system. At my terminal, when I need to, I simply run the command go-win11 and the Win11 VM spuns up.
Also, this fresh Win11 installation skips the "forced" MS-Account requirement, auto create for me a Local Administrator User with a password and need only to "interrupt" OOBE setup for the disk discovery and "where to install Win11 to" part.
Documenting this for the future-me when I move on to another machine and hopefully may help others too as sort of a guide. Note that this is a single-user machine/system.
- Downloads & Prerequisites
- Generating unattend.iso
- 1 - Host Preparation
- 1.1 Virtualization support
- 1.2 KVM modules
- 1.3 Packages
- 1.4 Tuned (Host optimization)
- 1.5 Modular libvirt daemons
- 1.6 User permissions
- 1.7 ACL for images
- 2 - Network (Layer 2 Bridge)
- 3 - Virt-Manager: New Win 11 VM Creation
- 3.1 ISOs & Installation logic
- 3.2 Virt-Manager settings
- 3.3 USB Passthrough
- 4 - Client Access & Automation
- 5 - Post-install cleanup
- 6 - Guest Tweaks & Performance Notes
Before starting, download these three critical ISO files and place them in our ISO directory (e.g., /var/lib/libvirt/images/).
| File | Description | Download Link |
|---|---|---|
| Windows 11 ISO | The official OS installer. | Microsoft.com |
| VirtIO Drivers | Required for KVM to see our Disk/Network. | Fedora People (Stable) |
| Unattend ISO | Automates the OOBE & Account Setup. | Schneegans Generator |
This repo includes a sample autounattend.xml file which has been pre-configured to:
- Bypass TPM/RAM/Secure Boot checks
- Create a local admin user (
username+password) to avoid the Microsoft Account requirement - Enable Remote Desktop automatically.
- Debloat almost 99% Windows apps
- Note that the setting includes a block on Win11 Pro product key which I do have
To use it:
- Open the
autounattend.xmlfrom this repo. - Edit the
<Password>,<User>and<Product_Key>fields accordingly - Go to the Schneegans Website
- Upload our edited XML (recommended to manually replicate the settings)
- Important!: At the bottom, choose "Download as unattend.iso"
- Why: KVM/QEMU reads answer files reliably from a mounted CD-ROM, whereas USB passthrough for answer files can be flaky during install
Run:
lscpu | grep Virtualization
# Expected: Virtualization: VT-x or AMD-VIf empty, enable virtualization in BIOS/UEFI.
For Arch:
zgrep CONFIG_KVM /proc/config.gzOther distros:
zgrep CONFIG_KVM /boot/config-$(uname -r)Look for CONFIG_KVM= y or m and the vendor modules. If missing, enable appropriate kernel options.
(Arch example)
sudo pacman -S qemu-full libvirt virt-install virt-manager virt-viewer \
edk2-ovmf swtpm qemu-img guestfs-tools libosinfoyay -S tuned
sudo systemctl enable --now tuned
sudo tuned-adm profile virtual-host
tuned-adm active
# Expect: Current active profile: virtual-hostSwitch to modular daemons (not really needed, monolithic works well too):
sudo systemctl disable --now libvirtd
sudo systemctl mask libvirtd
for drv in qemu interface network nodedev nwfilter secret storage; do
sudo systemctl enable virt${drv}d.service
sudo systemctl enable virt${drv}d{,-ro,-admin}.socket
done
sudo systemctl daemon-reload
sudo rebootAdd user to libvirt group:
sudo usermod -aG libvirt $USEREdit /etc/libvirt/libvirtd.conf:
unix_sock_group = "libvirt"
unix_sock_rw_perms = "0770"Set default URI in shell:
echo 'export LIBVIRT_DEFAULT_URI="qemu:///system"' >> ~/.bashrcUnlocked permissions to allow QEMU to run as user (sfarhan) instead of root.
Edit /etc/libvirt/qemu.conf
user = "sfarhan"
group = "sfarhan" # (or "kvm")Reason: This grants the VM permission to later write audio data to our local PulseAudio/PipeWire socket.
Grant access to /var/lib/libvirt/images:
sudo setfacl -R -m u:$USER:rwX /var/lib/libvirt/images
sudo setfacl -m d:u:$USER:rwx /var/lib/libvirt/imagesTip: If need to clear previous ACLs first:
sudo setfacl -R -b /var/lib/libvirt/imagesNow review new ACL permissions on the directory:
getfacl /var/lib/libvirt/images
getfacl: Removing leading '/' from absolute path names
# file: var/lib/libvirt/images
# owner: root
# group: root
user::rwx
user:sfarhan:rwx
group::--x
mask::rwx
other::--x
default:user::rwx
default:user:sfarhan:rwx
default:group::--x
default:mask::rwx
default:other::--x
Try accessing the /var/lib/libvirt/images directory again as a regular user.
touch /var/lib/libvirt/images/test_file
ls -l /var/lib/libvirt/images/
total 0
-rw-rw----+ 1 $USER $USER 0 Feb 12 21:34 test_file
Now have full access to the /var/lib/libvirt/images directory.
Objective: The VM must appear as a physical device on the LAN (Bridged) to bypass NAT and achieve full 5GbE throughput that I have.
- Physical:
enp7s0(10GbE Network Card) -> Slave - Logical:
br0(LAN-Bridge) -> Master - How to find out:
ip acommand
Create the bridge and bind the physical card.
# 1. Create the Bridge (The Master)
nmcli con add type bridge ifname br0 con-name "LAN-Bridge"
# 2. Bind the Physical Card (The Slave)
# Replace 'enp7s0' with our actual physical interface name
nmcli con add type bridge-slave ifname enp7s0 master br0 con-name "Ethernet-Uplink"
# 3. Force Autoconnect (For Headless Boot)
# Ensures the physical card wakes up and joins the bridge immediately on boot.
nmcli connection modify LAN-Bridge connection.autoconnect-slaves 1
# 4. Activate
nmcli con up "LAN-Bridge"graph TD
subgraph Host CachyOS
A[Physical Port / Ethernet-Uplink enp7s0] -->|Uplink| B(Bridge br0)
end
subgraph Virtual Machines
B -->|Virtual Cable| C[Windows 11 VM]
B -->|Virtual Cable| D[Future Ubuntu VM]
B -->|Virtual Cable| E[Future Home Assistant VM]
end
style B fill:#808080,stroke:#333,stroke-width:2px
When creating the VM in Virt-Manager for a fresh Win11 installation, we must attach three CD-ROMs during the initial setup to ensure a "hands-free" installation
In the "New VM" wizard, check "Customize configuration before install"
Then, in the "Add Hardware" section, ensure we have the following layout:
- SATA CDROM 1:
Windows11_Install.iso(The Installer) -> Boot Priority #1 - SATA CDROM 2:
virtio-win.iso(The Drivers) - SATA CDROM 3:
unattend.iso(The Script) - VirtIO Disk 1: Physical/Virtual Disk (Target) -> Boot Priority #2
The Installation Logic
-
Boot: The VM boots from CDROM 1 (Windows Installer)
-
Detection: Windows Setup silently scans CDROM 3 (
unattend.iso)- It finds
autounattend.xml - It bypasses the "Language" and "Key" screens, since I set up it up as such.
- It finds
-
Driver Injection: If using the Interactive Partitioning mode (recommended), click Load Driver -> Browse to CDROM 2 (
virtio-win) ->amd64->w11 -
Nuke: Delete all partitions on Disk 0 and click Next
-
Automation: The script takes over. It creates the user, sets the password, and logs us directly into the desktop (depending on our autounattend settings)
-
Step 1: Clicked "Create New Virtual Machine" (Monitor icon)
-
Step 2: (Install Method): Selected "Local install media (ISO image or CDROM)"
-
Step 3: (ISO/CO-Drive Selections): Browsed to Windows 11 ISO + other ISOs
-
Step 4: (Memory & CPU):
-
Memory: Typed
16384(16GB) - CPUs - Topology: 4 Cores / 2 Threads (adjust this accordingly to machine)
-
Memory: Typed
-
Step 5: (Storage):
- Action: Unchecked "Enable storage for this virtual machine" (Because we planned to add the physical SSD later manually)
-
Step 6: (Name & Network):
-
Name:
Win11 -
Network Selection: Selected "Bridge device"
$\rightarrow$ Device name:br0 - Crucial Checkbox: Checked "Customize configuration before install"
-
Name:
Once we hit Finish, the detailed config window opened.
- Chipset:
Q35 - Firmware:
UEFI x86_64: /usr/share/edk2-ovmf/...(Selected for UEFI/Secure Boot support)
- Model: Unchecked "Copy host CPU configuration"
$\rightarrow$ Typedhost-passthroughin the XML (or selected "Host-passthrough" in the dropdown) - Topology:
- Checked "Manually set CPU topology"
- Sockets: 1
- Cores: 4
- Threads: 2 (Total 8 vCPUs)
- Current allocation: 16384 MB
- Shared Memory: Disabled (Enabled for Looking Glass/IVSHMEM if needed later)
- Action: Clicked "Add Hardware"
- Type:
TPM - Model:
CRB(Command Response Buffer) - Version:
2.0 - Reason: Mandatory for Windows 11 installation checks.
-
Defined the physical pass-through
-
To find out our device details:
ls -l /dev/disk/by-id/ | grep sda lrwxrwxrwx 1 root root 9 Dec 6 07:34 ata-CT500MX500SSD1_2011E2936572 -> ../../sda
-
Action: Clicked "Add Hardware"
$\rightarrow$ Storage -
Device Type:
Block device(or edited XML directly) -
XML Tab: We pasted the specific block to reference the disk by ID:
<disk type='block' device='disk'> <driver name='qemu' type='raw' cache='none' io='native'/> <source dev='/dev/disk/by-id/ata-YOUR_SSD_ID'/> <target dev='vda' bus='virtio'/> </disk>
- Cache:
none(Prevents double-caching in RAM) - Discard:
unmap(Enables TRIM support)
- Cache:
-
Advanced Options (GUI):
- Disk bus:
VirtIO - Cache mode:
none - Discard mode:
unmap
- Disk bus:
- Action: Clicked "Add Hardware"
$\rightarrow$ Storage - Device Type: CDROM
- ISO: Browsed to
virtio-win.isoand unattend.iso
- Network source: Bridge device
br0 - Device model:
virtio
-
Enables graceful shutdown, clipboard sync, and filesystem freezing:
- Action: Add Hardware -> Channel
- Name: org.qemu.guest_agent.0
- Device Type: UNIX Socket
- Auto Socket: Checked
- Audio Backend: Switched from
spicetopulseaudioto bridge the sound without a viewer window.<sound model="ich9"> <audio id="1"/> <address type="pci" domain="0x0000" bus="0x00" slot="0x1b" function="0x0"/> </sound> <audio id='1' type='pulseaudio' serverName='/run/user/1000/pulse/native'> <input clientName='Win11-VM'/> <output clientName='Win11-VM'/> </audio>
1000is me (UserID)
For latency-sensitive devices like my Headset/Microphone (Jabra) and Webcam (Logitech), we use Libvirt USB Passthrough instead of RDP redirection. This physically attaches the USB device to the VM, allowing Windows to use its native drivers for better stability.
Identify USB Devices On my CachyOS Host, listing USB devices to find their Vendor/Product IDs:
lsusbExample Output:
Bus 001 Device 007: ID 0b0e:24c7 GN Netcom Jabra Link 380 <-- We want this
Bus 007 Device 002: ID 046d:094c Logitech, Inc. Brio 100 <-- We want this
Add to Virt-Manager
- Open Virt-Manager and double-click Win11 VM.
- Click the Lightbulb Icon (Show virtual hardware details).
- Click Add Hardware (Bottom Left).
- Select USB Host Device.
- Find specific device in the list (matching the ID from
lsusb).- Example:
001:007 GN Netcom Jabra Link 380
- Example:
- Click Finish.
- Repeat for the Webcam and Audio Adapter.
Verification
- Host Side: These devices will disappear from our Linux audio mixer (PulseAudio/PipeWire) because the VM has "stolen" them.
- Guest Side: Windows will detect them as local hardware. I can control the Jabra headset buttons and use the Logitech webcam software natively.
<hyperv mode="custom">
<relaxed state="on"/>
<vapic state="on"/>
<spinlocks state="on" retries="8191"/>
<vpindex state="on"/>
<runtime state="on"/>
<synic state="on"/>
<stimer state="on"/>
<tlbflush state="on"/>
<ipi state="on"/>
<avic state="on"/>
</hyperv>Required if router rejects KVM default MACs for static IP assignment. I only change the first 2 digits of the MAC Address to "00":
<mac address='00:54:::'/>This script checks the VM status, boots it if off, waits for the RDP port to open, and launches the client.
- Location:
~/.local/bin/go-win11 - Logic:
virsh start->nc -zloop ->xfreerdp3 / log-in script
#!/bin/bash
# Checks if VM is running, boots it, and connects via FreeRDP
# Place in ~./.local/bin/go-win11
...Using xfreerdp3 and created this Win11-script to actully auto-logged-in. The auto-launcher script above will call/look for this.
xfreerdp3 \
/v:192.168.0.16 \ #adjust IP accordingly!
/u:$USER \
/p:$PASSWORD \
/f \
/network:lan \
/size:2560x1440 \
/scale:100 \
/scale-desktop:120 \
/gfx:avc444 \
+clipboard \
/audio-mode:1 \
/cert:ignore \
/drive:CachyOS,/home/$USER/Shared \
/smart-sizing
# /log-level:DEBUGOnce we have verified that Windows is running and we can connect via RDP (go-win11) automatically, we should remove the installation media to speed up boot times.
- Shutdown the VM.
- Open Virt-Manager -> Details.
- Remove
SATA CDROM 1(Windows Installer). - Remove
SATA CDROM 2(VirtIO Drivers) - Optional, but recommended. - Remove
SATA CDROM 3(Unattend ISO). - Boot Options: Ensure
VirtIO Disk 1is now the only checked boot device.
Note: If we ever need to add more VirtIO hardware (e.g., a balloon driver or new NIC), we can re-attach the virtio-win.iso temporarily.
Performance Benchmark (Host <-> VM):
- Tool:
iperf3(10 Parallel Streams). Throughput: 37.5 Gbps. - Desired Output: VirtIO Para-virtualized network driver is functioning correctly, ensuring zero virtualization overhead for WAN traffic.
Below are the various settings, tweaks and changes I did over at Win11 side:
The VirtIO network driver defaults to "Large Send Offload" (LSO) being On. This causes lag spikes because the software driver waits to bundle packets.
- Action: Device Manager > Network adapters > Red Hat VirtIO Ethernet > Advanced
-
Set: Large Send Offload V2 (IPv4)
$\rightarrow$ Disabled -
Set: Large Send Offload V2 (IPv6)
$\rightarrow$ Disabled
Windows 11 "Balanced" mode tries to park CPU cores to save power. In a VM, this causes "wake-up latency" every time we move the mouse after a pause.
- Action: Control Panel > Power Options
- Select: High Performance. (If don't see it, click "Create a power plan")
- Why: This keeps our Ryzen cores active and ready to process RDP frames instantly.
RDP sometimes feels "floaty" because Windows applies software acceleration on top of our hardware mouse input.
- Action: Settings > Bluetooth & devices > Mouse > Additional mouse settings > Pointer Options
- Uncheck: "Enhance pointer precision"
Storage and network drivers to use Message Signaled Interrupts (MSI) instead of Line-based interrupts.
- Download the MSI Mode Utility v3 (standard tool in the virtualization community).
- Run it as Admin.
- Look for "Red Hat VirtIO SCSI" and "Red Hat VirtIO Ethernet".
- Ensure the box "MSI" is checked for both.
- If we change anything, click Apply and Reboot.
Windows 11 loves animations (fading windows, sliding menus, shadows). On a physical PC, these are handled by the GPU. In this VM, they are CPU-intensive and choke the RDP stream. Turning them off is the single most effective tweak.
- Inside the VM, press
Win + R. - Type
sysdm.cpland hit Enter. - Go to the Advanced tab.
- Under Performance, click Settings.
- Select "Adjust for best performance".
- Recommended tweak: Can check off "Smooth edges of screen fonts" (so text doesn't look jagged) and "Show thumbnails instead of icons" (so can see images), but leave everything else unchecked.
- Click Apply.
Result: Windows will pop open instantly instead of "fading" in.
Our client (xfreerdp3) is asking for high-quality AVC444 color (/gfx:avc444). We need to force Windows to prioritize this mode instead of trying to negotiate lower-quality bitmap modes.
- Inside the VM, press
Win + R, typegpedit.msc, and hit Enter - Navigate to:
Computer Configuration
$\rightarrow$ Administrative Templates$\rightarrow$ Windows Components$\rightarrow$ Remote Desktop Services$\rightarrow$ Remote Desktop Session Host$\rightarrow$ Remote Session Environment - Locate and Enable these three policies:
- "Prioritize H.264/AVC 444 Graphics mode for Remote Desktop Connections" (Matches our client setting)
- "Configure H.264/AVC hardware encoding for Remote Desktop Connections" (Forces the modern pipeline)
-
"Use the hardware default graphics adapter for all Remote Desktop Services sessions"
- Note: Since I don't have a physical GPU passed through, this forces Windows to use the most optimal path available (likely the VirtIO driver) rather than the legacy software rasterizer.
After changing these, open CMD as Admin and run:
gpupdate /forceThen Sign Out and Sign back in.
The default Windows 11 Photos App is heavy. It loads libraries and animations just to show a JPEG. In a VM without a GPU, this feels sluggish.
Recommendation: Install a lightweight image viewer like ImageGlass or restore the classic Windows Photo Viewer. They load instantly because they don't use 3D hardware acceleration for the UI.
-
Since I am using
freerdp3, I don't need complex Samba shares or XML edits to share files. I can just tell the RDP client to "bring a Linux folder with me" when it connects. -
Add the Drive flag line to our
xfreerdp3command block. We can map our entire home folder or just a specific "Downloads" folder./drive:CachyOS,/home/$USER \(This tells RDP: "Take the folder
/home/$USERon Linux and show it as a drive namedCachyOSinside Windows.") -
Open File Explorer > This PC. We will see a "Network Drive" named CachyOS. We can drag and drop files instantly.
