Skip to content
View in the app

A better way to browse. Learn more.

Unraid

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Complete 2026 Guide: Windows 11 Gaming VM with Secure Boot, TPM 2.0, and Anti-Cheat Support on Unraid (Call of Duty / Ricochet / EAC / Vanguard / BattlEye)

Featured Replies

# The Complete 2026 Guide: Windows 11 Gaming VM with Secure Boot, TPM 2.0, and Anti-Cheat Support on Unraid (Call of Duty / Ricochet / EAC / Vanguard / BattlEye)

**Tested on**: Unraid 7.1.4, AMD Ryzen 9 7950X, ASUS TUF GAMING B650M-PLUS, NVIDIA RTX 3060, WD Black SN850X 2TB NVMe (bare-metal Windows install via PCI passthrough)

**Result**: Smoother frame times than I ever got even running Windows directly on bare metal. Call of Duty (with Ricochet anti-cheat) launches and plays without stutter or attestation errors. This guide will save you the 6+ hours of debugging it took to get here.

---

## TL;DR (jump to the fix)

Modern anti-cheat (Ricochet, EAC, Vanguard, BattlEye) requires:
- **UEFI boot** with **Secure Boot enabled and enforcing** (not just present)
- **TPM 2.0** properly initialized
- **A "real-looking" SMBIOS** (not "QEMU / EDK II")
- **Hardware that doesn't scream VM** in CPUID

Unraid 7.1.x ships OVMF firmware **without Secure Boot or TPM compiled in** (`OVMF_CODE-pure-efi-tpm.fd` is misleadingly named). This is the root of 90% of VM gaming pain in 2026.

The fix:
1. Extract Fedora's `OVMF_CODE_4M.secboot.fd` + `OVMF_VARS_4M.secboot.fd` (has SB + TPM + MS keys pre-enrolled)
2. Mount a 1GB hugetlbfs (Unraid doesn't do this by default)
3. Pin VM to a **single CCD** on Ryzen (massive latency improvement)
4. Spoof SMBIOS to look like real hardware
5. Set Windows to Ultimate Performance, disable HAGS

Full XML and commands below.

---

## Why this guide exists — let's normalize gaming VMs again

A few years ago, gaming VMs on Unraid / Proxmox / Arch with VFIO passthrough were thriving. You'd build a single tower with all your storage, run Plex, run a dozen Docker containers, and have a Windows gaming VM that performed within 5% of bare metal.

Then anti-cheat got aggressive. Ricochet, Vanguard, and the latest Easy Anti-Cheat started checking Secure Boot state, TPM attestation, and SMBIOS strings. Suddenly half the gaming VM guides on the internet stopped working. Posts from 2022-2023 telling you to use OVMF without TPM, or boot a vdisk with SeaBIOS, will make COD refuse to launch.

This guide is a 2026 update that gets you a fully attested, anti-cheat-compatible Windows 11 gaming VM. You can run your home server **and** play AAA multiplayer titles without dual-booting. **One machine, many uses.** That's what Unraid is supposed to be.

---

## You don't have to debug this alone — use Claude Code

Real talk: this guide exists because I went through 6+ hours of debugging with **Claude Code** (Anthropic's terminal-based AI assistant) helping me at every step. It read my XML, parsed libvirt error logs, suggested OVMF builds, walked me through OVMF firmware menus, and rewrote my XML 11 times as we discovered new issues.

**If you're going to do this yourself, run Claude Code in your Unraid SSH session.** It can:

- Read your current VM XML and tell you what's missing
- Parse `/var/log/libvirt/qemu/<vm>.log` errors and suggest fixes
- Generate the exact `<sysinfo>` SMBIOS block for your motherboard
- Check `/proc/cmdline`, `/sys/kernel/mm/hugepages/`, `mdcmd status`, and other diagnostic outputs in real time
- Generate `nano` keystroke instructions if you've never edited Linux config files
- Write your `/boot/config/go` modifications correctly

With **Unraid's own MCP API** (now in public beta), an AI agent can read your system state, edit XMLs, mount filesystems, and basically perform this entire process autonomously. We're at the point where setting up a gaming VM should be a 5-minute conversation, not a 6-hour rabbit hole. The tools are there — use them.

[Claude Code](https://claude.ai/code) | [Unraid API](https://docs.unraid.net/API/) | [Anthropic Console](https://console.anthropic.com)

---

## Hardware tested

| Component | Spec |
|-----------|------|
| **CPU** | AMD Ryzen 9 7950X (16 cores / 32 threads, two CCDs) |
| **Motherboard** | ASUS TUF GAMING B650M-PLUS |
| **GPU** | NVIDIA RTX 3060 (single GPU, passed through) |
| **RAM** | 32 GB DDR5 |
| **Boot drive (VM)** | WD Black SN850X 2TB NVMe (PCIe passthrough — Windows installed bare-metal style) |
| **Host OS** | Unraid 7.1.4 (QEMU 9.2.3, libvirt 11.2.0, kernel 6.12.24) |

This guide adapts to any AMD Ryzen 5000/7000/9000 + Nvidia/AMD GPU. Intel users: same OVMF/SMBIOS/TPM steps, different CPU pinning math.

---

## Common error messages this guide solves

If you've Googled any of these, you're in the right place:

- `internal error: Unable to find any usable hugetlbfs mount for 1048576 KiB`
- `operation failed: unable to find any master var store for loader`
- `Numerical result out of range` (cgroup cpuset)
- `unable to write to '/sys/fs/cgroup/.../cpuset.cpus': Numerical result out of range`
- COD: **"BIOS firmware: Update Required"** / **"Failed Attestation Status will be applied"**
- `Get-Tpm` returns `False` for everything in Windows
- `msinfo32` shows **Secure Boot State: Off** despite enrolling keys
- VM boots TianoCore but Windows won't load (no boot device)
- VM crashes 28 seconds after start, libvirtd kills it
- `mt76x2u ... failed:-110` spamming dmesg
- Stuttering / micro-lag in games despite high FPS
- `Confirm-SecureBootUEFI` returns `False`

---

## Part 1 — BIOS settings (do this first)

### ASUS TUF B650M-PLUS specifically

Boot into BIOS (Del at POST) → F7 for Advanced Mode.

| Setting | Path | Value |
|---------|------|-------|
| **Ai Overclock Tuner** | Ai Tweaker | **Auto** (or just enable EXPO if you want memory speed; do NOT use any "Gaming" / "Eco" preset — these can disable a CCD) |
| **SMT Control** | Advanced → AMD CBS → CPU Common Options → Performance | **Auto** (keeps all 32 threads) |
| **CCD Control** | Advanced → AMD CBS → CPU Common Options → Performance | **Auto** (keeps both CCDs) |
| **Global C-state Control** | Advanced → AMD CBS → CPU Common Options | **Disabled** or limit to C2 (reduces latency spikes) |
| **Power Supply Idle Control** | Advanced → AMD CBS → CPU Common Options | **Typical** |
| **PCIe ASPM** | Advanced → PCH Configuration | **Disabled** |
| **fTPM / AMD CPU fTPM** | Advanced → AMD fTPM Configuration | **Enabled** (host TPM, separate from vTPM) |
| **CSM** | Boot → CSM | **Disabled** (UEFI only) |
| **IOMMU** | Advanced → AMD CBS → NBIO Common Options | **Enabled** |
| **SVM Mode** | Advanced → CPU Configuration | **Enabled** (AMD-V virtualization) |

### Verify after BIOS save + reboot

```bash
lscpu | head -20
# Expected:
# CPU(s): 32
# Thread(s) per core: 2
# Core(s) per socket: 16
```

If you only see 8 or 16 CPUs, an EZ Tuning preset has nuked one of your CCDs or SMT. Go back to BIOS and check those settings.

---

## Part 2 — Hugepages setup (massive performance boost)

1GB hugepages eliminate TLB misses and cut memory latency dramatically. This is the single biggest factor in eliminating VM gaming stutter.

### Edit `/boot/syslinux/syslinux.cfg`

Append `default_hugepagesz=1G hugepagesz=1G hugepages=24` to the kernel command line of the default Unraid OS label. Example:

```
label Unraid OS
menu default
kernel /bzimage
append pcie_acs_override=multifunction vfio_iommu_type1.allow_unsafe_interrupts=1 default_hugepagesz=1G hugepagesz=1G hugepages=24 initrd=/bzroot
```

This pre-allocates 24 GB of 1GB hugepages at boot — enough for a 22 GB VM with headroom.

### Mount hugetlbfs (Unraid doesn't do this for 1GB pages)

Edit `/boot/config/go`. Add **before** `emhttp`:

```bash
# Mount 1GB hugetlbfs for VMs
mkdir -p /dev/hugepages1G
mount -t hugetlbfs -o pagesize=1G hugetlbfs /dev/hugepages1G
```

### Reboot and verify

```bash
cat /proc/cmdline | grep huge
# Should show: ... default_hugepagesz=1G hugepagesz=1G hugepages=24 ...

cat /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages
# Should show: 24

mount | grep huge
# Should show TWO mounts:
# hugetlbfs on /dev/hugepages (default 2MB)
# hugetlbfs on /dev/hugepages1G (pagesize=1024M)
```

⚠️ Without the `mount` step, libvirt errors with `Unable to find any usable hugetlbfs mount for 1048576 KiB` even though the kernel has the hugepages allocated.

---

## Part 3 — OVMF firmware (the part that breaks Secure Boot)

### Why Unraid's bundled OVMF doesn't work

Unraid 7.1.x ships:
- `/usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi.fd`
- `/usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi-tpm.fd`
- corresponding VARS files

**The "pure-efi" naming is misleading.** These builds are compiled WITHOUT the Secure Boot enforcement code path. You can enroll Microsoft keys all day, and the firmware state will never flip to Enabled. Windows will report `Secure Boot State: Off`.

### The fix — extract Fedora's edk2-ovmf via Docker

Fedora ships `OVMF_CODE_4M.secboot.fd` with **SecureBoot + TPM + SMM all compiled in**, AND `OVMF_VARS_4M.secboot.fd` with **Microsoft keys pre-enrolled**. No manual key enrollment needed.

```bash
mkdir -p /mnt/user/isos/ovmf

docker run --rm -v /mnt/user/isos/ovmf:/out fedora:latest bash -c '
dnf install -y edk2-ovmf qemu-img &&
qemu-img convert -f qcow2 -O raw /usr/share/edk2/ovmf/OVMF_CODE_4M.secboot.qcow2 /out/OVMF_CODE_4M.secboot.fd &&
qemu-img convert -f qcow2 -O raw /usr/share/edk2/ovmf/OVMF_VARS_4M.secboot.qcow2 /out/OVMF_VARS_4M.secboot.fd &&
chmod 644 /out/OVMF_*4M*
'

ls -la /mnt/user/isos/ovmf/
# Expected:
# OVMF_CODE_4M.secboot.fd ~3.5 MB
# OVMF_VARS_4M.secboot.fd ~540 KB
```

Storing them in `/mnt/user/isos/ovmf/` keeps them on the array (persistent across reboots — `/usr/share` is tmpfs on Unraid).

### Clean up old NVRAM (if you tried previous OVMF builds)

```bash
rm -f /etc/libvirt/qemu/nvram/<your-vm-uuid>_VARS-pure-efi-tpm.fd
rm -f /etc/libvirt/qemu/nvram/<your-vm-uuid>_VARS.secboot.fd
```

(Replace `<your-vm-uuid>` with the value from your VM XML's `<uuid>` tag. libvirt will copy a fresh VARS file with MS keys baked in on next start.)

---

## Part 4 — Complete VM XML

Replace `<your-vm-uuid>` with your VM's actual UUID. Adjust the GPU/NVMe PCI addresses and USB devices to match `lspci -nn` and `lsusb` on your system.

```xml
<?xml version='1.0' encoding='UTF-8'?>
<domain type='kvm'>
<name>Windows 11 Gaming VM SSD - Secure Boot</name>
<uuid>YOUR-UUID-HERE</uuid>
<memory unit='KiB'>23068672</memory>
<currentMemory unit='KiB'>23068672</currentMemory>
<memoryBacking>
<hugepages>
<page size='1048576' unit='KiB'/>
</hugepages>
<nosharepages/>
<locked/>
</memoryBacking>
<vcpu placement='static'>16</vcpu>
<iothreads>1</iothreads>
<cputune>
<!-- Pin all 16 vCPUs to CCD1 (cores 8-15 + threads 24-31)
Host gets CCD0 (cores 0-7 + threads 16-23) for Unraid -->
<vcpupin vcpu='0' cpuset='8'/>
<vcpupin vcpu='1' cpuset='24'/>
<vcpupin vcpu='2' cpuset='9'/>
<vcpupin vcpu='3' cpuset='25'/>
<vcpupin vcpu='4' cpuset='10'/>
<vcpupin vcpu='5' cpuset='26'/>
<vcpupin vcpu='6' cpuset='11'/>
<vcpupin vcpu='7' cpuset='27'/>
<vcpupin vcpu='8' cpuset='12'/>
<vcpupin vcpu='9' cpuset='28'/>
<vcpupin vcpu='10' cpuset='13'/>
<vcpupin vcpu='11' cpuset='29'/>
<vcpupin vcpu='12' cpuset='14'/>
<vcpupin vcpu='13' cpuset='30'/>
<vcpupin vcpu='14' cpuset='15'/>
<vcpupin vcpu='15' cpuset='31'/>
<emulatorpin cpuset='0,16'/>
<iothreadpin iothread='1' cpuset='0,16'/>
</cputune>
<os>
<type arch='x86_64' machine='pc-q35-9.2'>hvm</type>
<loader readonly='yes' secure='yes' type='pflash' format='raw'>/mnt/user/isos/ovmf/OVMF_CODE_4M.secboot.fd</loader>
<nvram template='/mnt/user/isos/ovmf/OVMF_VARS_4M.secboot.fd' format='raw'>/etc/libvirt/qemu/nvram/YOUR-UUID-HERE_VARS_4M.secboot.fd</nvram>
<bootmenu enable='yes' timeout='5000'/>
<smbios mode='sysinfo'/>
</os>
<sysinfo type='smbios'>
<bios>
<entry name='vendor'>American Megatrends Inc.</entry>
<entry name='version'>2429</entry>
<entry name='date'>11/14/2024</entry>
<entry name='release'>5.29</entry>
</bios>
<system>
<entry name='manufacturer'>ASUS</entry>
<entry name='product'>System Product Name</entry>
<entry name='version'>System Version</entry>
<entry name='serial'>System Serial Number</entry>
<entry name='uuid'>YOUR-UUID-HERE</entry>
<entry name='sku'>SKU</entry>
<entry name='family'>To be filled by O.E.M.</entry>
</system>
<baseBoard>
<entry name='manufacturer'>ASUSTeK COMPUTER INC.</entry>
<entry name='product'>TUF GAMING B650M-PLUS</entry>
<entry name='version'>Rev 1.xx</entry>
<entry name='serial'>230123456789</entry>
<entry name='asset'>Default string</entry>
<entry name='location'>Default string</entry>
</baseBoard>
<chassis>
<entry name='manufacturer'>Default string</entry>
<entry name='version'>Default string</entry>
<entry name='serial'>Default string</entry>
<entry name='asset'>Default string</entry>
<entry name='sku'>Default string</entry>
</chassis>
</sysinfo>
<features>
<acpi/>
<apic/>
<hyperv mode='custom'>
<relaxed state='on'/>
<vapic state='on'/>
<spinlocks state='on' retries='8191'/>
<vpindex state='on'/>
<synic state='on'/>
<stimer state='on'/>
<reset state='on'/>
<frequencies state='on'/>
<vendor_id state='on' value='none'/>
</hyperv>
<kvm>
<hidden state='on'/>
</kvm>
<ioapic driver='kvm'/>
<smm state='on'/>
</features>
<cpu mode='host-passthrough' check='none' migratable='on'>
<topology sockets='1' dies='1' clusters='1' cores='8' threads='2'/>
<cache mode='passthrough'/>
<feature policy='require' name='topoext'/>
<feature policy='require' name='invtsc'/>
<feature policy='disable' name='hypervisor'/>
</cpu>
<clock offset='localtime'>
<timer name='hpet' present='no'/>
<timer name='hypervclock' present='yes'/>
<timer name='tsc' present='yes' mode='native'/>
</clock>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<devices>
<emulator>/usr/local/sbin/qemu</emulator>
<!-- Win11 ISO + virtio-win drivers (keep until Windows is happy, then can remove) -->
<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/>
<source file='/mnt/user/isos/Win11_23H2_EnglishInternational_x64v2.iso'/>
<target dev='hda' bus='sata'/>
<readonly/>
<boot order='2'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/>
<source file='/mnt/user/isos/virtio-win-0.1.285-1.iso'/>
<target dev='hdb' bus='sata'/>
<readonly/>
<address type='drive' controller='0' bus='0' target='0' unit='1'/>
</disk>
<controller type='usb' index='0' model='qemu-xhci' ports='15'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
</controller>
<controller type='pci' index='0' model='pcie-root'/>
<!-- Multiple PCIe root ports needed for hostdev attachments -->
<controller type='pci' index='1' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='1' port='0x8'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x0' multifunction='on'/>
</controller>
<controller type='pci' index='2' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='2' port='0x9'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
</controller>
<controller type='pci' index='3' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='3' port='0xa'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
</controller>
<controller type='pci' index='4' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='4' port='0xb'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x3'/>
</controller>
<controller type='pci' index='5' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='5' port='0xc'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x4'/>
</controller>
<controller type='virtio-serial' index='0'>
<address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x0'/>
</controller>
<controller type='sata' index='0'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x1f' function='0x2'/>
</controller>
<interface type='bridge'>
<mac address='52:54:00:78:34:57'/>
<source bridge='br0'/>
<model type='virtio-net'/>
<driver name='vhost' queues='8'/>
<address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
</interface>
<serial type='pty'>
<target type='isa-serial' port='0'>
<model name='isa-serial'/>
</target>
</serial>
<console type='pty'>
<target type='serial' port='0'/>
</console>
<channel type='unix'>
<target type='virtio' name='org.qemu.guest_agent.0'/>
<address type='virtio-serial' controller='0' bus='0' port='1'/>
</channel>
<input type='mouse' bus='ps2'/>
<input type='keyboard' bus='ps2'/>
<tpm model='tpm-crb'>
<backend type='emulator' version='2.0' persistent_state='yes'/>
</tpm>
<audio id='1' type='none'/>
<!-- GPU passthrough: RTX 3060 + audio -->
<hostdev mode='subsystem' type='pci' managed='yes'>
<driver name='vfio'/>
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
</source>
<rom file='/mnt/disk1/Media/RTX 3060 vbios TP.rom'/>
<address type='pci' domain='0x0000' bus='0x02' slot='0x00' function='0x0'/>
</hostdev>
<hostdev mode='subsystem' type='pci' managed='yes'>
<driver name='vfio'/>
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x1'/>
</source>
<address type='pci' domain='0x0000' bus='0x03' slot='0x00' function='0x0'/>
</hostdev>
<!-- NVMe passthrough (managed='yes' so libvirt auto-detaches from host) -->
<hostdev mode='subsystem' type='pci' managed='yes'>
<driver name='vfio'/>
<source>
<address domain='0x0000' bus='0x0b' slot='0x00' function='0x0'/>
</source>
<address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
</hostdev>
<!-- USB device passthroughs (mouse, keyboard, etc.) -->
<hostdev mode='subsystem' type='usb' managed='no'>
<source>
<vendor id='0x0b05'/>
<product id='0x19af'/>
</source>
<address type='usb' bus='0' port='1'/>
</hostdev>
<watchdog model='itco' action='reset'/>
<memballoon model='none'/>
</devices>
</domain>
```

### Why each part of this XML matters

| Element | Purpose |
|---------|---------|
| `<loader secure='yes'>` | Tells libvirt to enable SMM-enforced Secure Boot |
| `<smm state='on'/>` | Required for Secure Boot enforcement |
| `<bootmenu>` | Lets you press ESC at boot for OVMF firmware menu |
| `<sysinfo>` SMBIOS spoof | Makes Windows see "ASUS TUF" instead of "QEMU" |
| `<feature policy='disable' name='hypervisor'/>` | Removes the CPUID hypervisor flag (most VM detection's first check) |
| `<kvm><hidden state='on'/>` | Hides KVM CPUID leaf |
| `<hyperv>` enlightenments | Tells Windows it's on Hyper-V, lets KVM optimize Windows scheduling |
| `<vendor_id state='on' value='none'/>` | Hides "KVMKVMKVM" CPU vendor string |
| `<page size='1048576'>` hugepages | 1GB pages → near-zero TLB misses |
| `<locked/>` memory | Host can't swap/page VM memory |
| Single CCD pinning (cores 8-15, threads 24-31) | All vCPUs share one L3 cache → no cross-CCD latency penalty |
| `emulatorpin` to CCD0 | QEMU helper threads run on host's cores, not stealing yours |
| `iothreadpin` | Dedicated I/O thread, off vCPU cores |
| `invtsc` feature | Stable timer for Windows |
| `<tpm model='tpm-crb'>` | Windows 11 prefers CRB over TIS |
| `persistent_state='yes'` on TPM | Survives VM restarts (BitLocker stays happy) |
| `managed='yes'` on NVMe hostdev | libvirt auto-detaches from host nvme driver, no VFIO bind required |

---

## Part 5 — Windows-side optimizations

After the VM boots into Windows:

### Power plan: Ultimate Performance

```powershell
powercfg -duplicatescheme e9a42b02-d5df-448d-aa00-03f14749eb61
```

Then **Control Panel → Power Options → Ultimate Performance** (selected).

### Disable HAGS (Hardware-accelerated GPU scheduling)

Settings → System → Display → Graphics → Default settings → toggle **off**.

HAGS sometimes causes stutter in VMs, especially with Nvidia drivers + COD.

### Nvidia control panel

- Power management mode: **Prefer Maximum Performance**
- Vertical sync: **Off** (use in-game)
- Low Latency Mode: **Ultra**

### Disable Game Mode + Game Bar

Settings → Gaming → Game Mode → **Off**.
Game Bar can also cause hitches in long sessions.

### Verify Secure Boot + TPM

```powershell
Confirm-SecureBootUEFI # Should return True
Get-Tpm # All fields should be True
```

`msinfo32` → **Secure Boot State: On**, **BIOS Mode: UEFI**.

### Verify SMBIOS spoof

```powershell
Get-WmiObject Win32_ComputerSystem | Select Manufacturer, Model
Get-WmiObject Win32_BIOS | Select Manufacturer, SMBIOSBIOSVersion, ReleaseDate
Get-WmiObject Win32_BaseBoard | Select Manufacturer, Product, Version
```

Should show "ASUS / TUF GAMING B650M-PLUS / American Megatrends 2429" — NOT "QEMU / EDK II".

---

## Performance results

Subjective gameplay smoothness is night and day:

- **Before this guide**: stutters every 1-2 seconds, FPS hits the cap but feels janky
- **After**: cleaner frame times than I get on bare metal Windows on the exact same hardware

Why VM beats bare metal here: Unraid scheduler isolation + 1GB hugepages + locked memory means the VM has zero contention from background OS noise. On bare metal Windows, every Defender scan, telemetry hit, or Windows Update cycle introduces frame time variance. In the VM, all that lives on the host (Unraid), separated by hardware boundaries.

---

## Troubleshooting reference

### `Unable to find any usable hugetlbfs mount for 1048576 KiB`

Hugepages allocated but not mounted. See Part 2 — `/dev/hugepages1G` mount.

### `unable to find any master var store for loader`

NVRAM template path missing or wrong. Check `<nvram template='...'>` points at an existing OVMF VARS file.

### `Numerical result out of range` (cpuset)

Pinning to CPU IDs that don't exist. Run `nproc` on host. If it shows 8 instead of 32, BIOS has SMT or a CCD disabled. Check `lscpu | head -20`.

### Get-Tpm all False, but TPM device shown in XML

OVMF doesn't have TPM compiled in. Use Fedora's `OVMF_CODE_4M.secboot.fd` — see Part 3.

### COD: "BIOS firmware: Update Required" / Failed Attestation

- Verify SMBIOS spoof actually applied (Windows-side WMI checks above)
- Sign in with Microsoft account
- Update Windows to latest
- Clear and re-init TPM in Windows (Settings → Privacy & Security → Security processor troubleshooting)
- Note: Activision officially does not support VMs. Some weeks/builds work, others don't. EAC and BattlEye are more forgiving.

### VM crashes 28 seconds after start

Often `<watchdog action='reset'/>` triggering because something inside the VM stopped responding (GPU driver crash, NVMe detach failure). Check `/var/log/libvirt/qemu/<vm>.log` for hints.

### Stuttering despite high FPS

You're spanning CCDs. Reduce vCPUs and pin to one CCD. See Part 4 cputune.

### NVMe passthrough fails to start

Ensure NVMe is NOT assigned to array, NOT in any pool, NOT mounted. With `managed='yes'`, libvirt handles detach. Check Main tab in webGUI — the drive should be in "Unassigned Devices", not the array.

---

## Diagnostic commands cheatsheet

```bash
# CPU topology
lscpu | head -20
nproc
cat /proc/cmdline

# Hugepages
cat /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages
cat /sys/kernel/mm/hugepages/hugepages-1048576kB/free_hugepages
mount | grep huge

# CCD layout (which CPUs share L3 cache)
cat /sys/devices/system/cpu/cpu0/cache/index3/shared_cpu_list
cat /sys/devices/system/cpu/cpu8/cache/index3/shared_cpu_list

# OVMF files available
ls /usr/share/qemu/ovmf-x64/ /mnt/user/isos/ovmf/ 2>/dev/null

# VM run state
virsh list --all
virsh dumpxml "Windows 11 Gaming VM SSD - Secure Boot"

# VM start log (most recent attempt)
tail -150 "/var/log/libvirt/qemu/Windows 11 Gaming VM SSD - Secure Boot.log"

# Live QEMU command line
ps -ef | grep qemu-system

# TPM emulator log
cat "/var/log/swtpm/libvirt/qemu/Windows 11 Gaming VM SSD - Secure Boot-swtpm.log"

# Array / disk status
mdcmd status | grep -E "diskNumber|diskName|rdevStatus|rdevName" | head -30

# Restart libvirt
/etc/rc.d/rc.libvirt restart
```

---

## Appendix A — Why manual Secure Boot key enrollment doesn't work on Unraid (and what we tried first)

Most older guides tell you to manually enroll Microsoft Secure Boot keys via the OVMF firmware menu. **This does not work on Unraid 7.x.** We spent 2 hours discovering this. Here's the dead-end path so you don't repeat it:

### Step 1 — Download Microsoft's Secure Boot certs (this part is tricky)

Microsoft's official cert download URLs return **403 Forbidden** for `wget`/`curl` even with browser user-agent headers:

```bash
# THIS DOES NOT WORK
curl -A "Mozilla/5.0" "https://go.microsoft.com/fwlink/?LinkId=321185"
# Returns HTML 403 page disguised as a .crt file
```

Use Microsoft's **official GitHub mirror** instead — these URLs work without auth:

```bash
mkdir -p /tmp/mskeys && cd /tmp/mskeys

curl -L -o MicCorKEKCA2011.der "https://raw.githubusercontent.com/microsoft/secureboot_objects/main/PreSignedObjects/KEK/Certificates/MicCorKEKCA2011_2011-06-24.der"
curl -L -o MicWinProPCA2011.der "https://raw.githubusercontent.com/microsoft/secureboot_objects/main/PreSignedObjects/DB/Certificates/MicWinProPCA2011_2011-10-19.der"
curl -L -o MicCorUEFCA2011.der "https://raw.githubusercontent.com/microsoft/secureboot_objects/main/PreSignedObjects/DB/Certificates/MicCorUEFCA2011_2011-06-27.der"

# Verify they're real certs (~1500-2000 bytes each, NOT 4410-byte HTML)
file *.der
ls -la *.der
```

You should see `Certificate, Version=3` for each. If you see `HTML document`, you got the wrong URLs.

### Step 2 — Get the certs onto a disk OVMF can read

Unraid doesn't have `mkisofs` or `genisoimage`. Build a FAT32 disk image with a GPT partition table (raw FAT on a small disk often gets ignored by OVMF's file browser):

```bash
# Build a 64MB image with GPT + FAT32 partition
dd if=/dev/zero of=/mnt/user/isos/secureboot-keys.img bs=1M count=64
parted -s /mnt/user/isos/secureboot-keys.img mklabel gpt
parted -s /mnt/user/isos/secureboot-keys.img mkpart ESP fat32 1MiB 100%
parted -s /mnt/user/isos/secureboot-keys.img set 1 esp on

# Format the partition
LOOP=$(losetup -f --show -P /mnt/user/isos/secureboot-keys.img)
mkfs.vfat -F 32 "${LOOP}p1"

# Copy certs in
mkdir -p /mnt/sbkeys
mount "${LOOP}p1" /mnt/sbkeys
cp /tmp/mskeys/*.der /mnt/sbkeys/
ls -la /mnt/sbkeys/

# Cleanup
umount /mnt/sbkeys
losetup -d "$LOOP"
```

Attach this image to the VM as `device='disk'` (not cdrom) on the SATA bus, replacing the Win11 ISO temporarily.

### Step 3 — Boot into OVMF firmware menu and try to enroll keys

Add `<bootmenu enable='yes' timeout='5000'/>` to your `<os>` block, start VM, hammer ESC at the TianoCore splash. Navigate:

- **Device Manager** → **Secure Boot Configuration**
- Tick **Attempt Secure Boot**
- Set **Secure Boot Mode** → **Custom Mode**
- **Custom Secure Boot Options** → enroll in this order: **DB → KEK → PK**
- DB Options → Enroll Signature Using File → MicWinProPCA2011.der → Commit
- DB Options → again → MicCorUEFCA2011.der → Commit
- KEK Options → Enroll KEK Using File → MicCorKEKCA2011.der → Commit
- PK Options → Enroll PK Using File → MicCorKEKCA2011.der → Commit (PK enrollment activates Secure Boot)

### Step 4 — Discover that none of this matters

After enrollment, **Secure Boot State** still shows **Disabled** in the OVMF menu, and Windows reports `Secure Boot State: Off`. Why?

```bash
# Check if the OVMF binary even has Secure Boot strings compiled in
grep -aoc "TPM2\|TCG2" /usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi-tpm.fd
# Returns 0 — but this test is unreliable since OVMF strings are compressed
```

The actual test is "does the OVMF firmware menu have a working **Attempt Secure Boot** checkbox after key enrollment". On Unraid's bundled `OVMF_CODE-pure-efi-tpm.fd`, the answer is no. The "pure-efi" naming is a giveaway: this build excludes the SecureBoot enforcement code path. Same for `OVMF_CODE.secboot.fd` (the 2MB variant) which Fedora also ships — it has the strings but lacks TPM.

### The right answer

Use Fedora's **`OVMF_CODE_4M.secboot.fd`** (the 4M variant) — it has SecureBoot + TPM + SMM all compiled in, AND `OVMF_VARS_4M.secboot.fd` has Microsoft keys **pre-enrolled** so you skip the entire firmware menu dance. See Part 3 of this guide.

Total time saved by knowing this upfront: ~2 hours.

---

## Appendix B — Lessons learned about anti-cheat in 2026

What works (with this guide):
- Easy Anti-Cheat (Fortnite, Apex, etc.)
- BattlEye (PUBG, R6 Siege, DayZ, ARK, Tarkov*)
- Vanguard (Valorant) — usually
- ⚠️ Ricochet (Call of Duty) — works but Activision actively blocks VMs; menu loads, gameplay smooth, but you may get attestation warnings or random kicks

\*Tarkov is hit or miss; depends on the BattlEye config of the day.

What still doesn't work:
- Faceit (CS2 third-party) — explicitly blocks VMs
- ESEA — same
- Some VAC-secured competitive matchmaking modes

Activision/Microsoft attestation flow:
1. VM boots, OVMF measures SecureBoot keys + bootloader into TPM PCRs
2. Windows boots, measures kernel and driver loads
3. Ricochet collects PCR values, sends to Microsoft's attestation cloud API
4. API checks PCR values against database of known-good real-hardware signatures
5. VM TPM measurements don't match any real OEM motherboard → "Failed Attestation"

There is no XML you can write that bypasses this — it's cryptographic comparison against Microsoft's database. SMBIOS spoofing fixes the first-pass surface checks, which gets you into the game menu and matches in most cases. But TPM attestation is the wall.

Workarounds that sometimes help:
- Sign in with a Microsoft account (some attestation calls trust the linked identity)
- Update Windows fully (newer attestation client is more permissive)
- Accept that COD VM gameplay is best-effort

---

## Conclusion

In 2026, modern anti-cheat means gaming VMs need to look exactly like real hardware. The default Unraid OVMF doesn't do this. The good news: Fedora ships a perfect OVMF for free, and with the right XML you can get smoother gameplay than bare metal.

Key takeaways:
1. Use **Fedora's OVMF_CODE_4M.secboot.fd** (Unraid's bundled OVMF won't work)
2. Mount **1GB hugetlbfs manually** (Unraid only does 2MB by default)
3. Pin to **a single CCD** on Ryzen
4. Spoof **SMBIOS** with realistic motherboard info
5. Hide the **hypervisor CPUID flag**
6. Use **AI assistants** like Claude Code to handle the debugging loop

Gaming VMs are absolutely viable in 2026. They had a rough patch when anti-cheat got aggressive, but the tools are catching up. Let's normalise this — there's no reason to maintain a separate gaming PC if your Unraid box is already a beast.

---

## Credits and resources

- **Built with**: [Claude Code](https://claude.ai/code) (Anthropic Sonnet 4.7, 1M context window) — debugged with me line-by-line for ~6 hours
- **Unraid**: [forums.unraid.net](https://forums.unraid.net), [docs.unraid.net](https://docs.unraid.net)
- **Unraid API/MCP**: [docs.unraid.net/API/](https://docs.unraid.net/API/) — connect Claude or another agent for autonomous VM management
- **OVMF**: [Fedora's edk2-ovmf package](https://packages.fedoraproject.org/pkgs/edk2/edk2-ovmf/)
- **SMBIOS spec**: [DMTF System Management BIOS](https://www.dmtf.org/standards/smbios)
- **Anti-cheat compatibility**: [areweanticheatyet.com](https://areweanticheatyet.com)

If this guide helped you, drop a reply on the Unraid forum thread so others can find it. The more of us who post working configs in 2026, the easier this gets for the next person.

**Tags**: #unraid #vfio #gaming-vm #windows11 #secure-boot #tpm #call-of-duty #ricochet #anti-cheat #ryzen #7950x #hugepages #ovmf #kvm #qemu #libvirt

  • 1 month later...

I did everything and more than that (nvme passthrough, nic passthrough, GPU passthrough) and doesn't even work

  • 3 weeks later...

Hello and thanks for the very detailed guide.

Unfortunately, my VM is still detected by EAC (in Rocket League).
I am stil investigating how EAC managed to do it.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.