Proxmox VEXXVI · Windows GuestsFleets of Windows guests
Windows guest performance
What you'll learn
- Choose a CPU type for a Windows guest and state what the kvm64 default costs
- Size vCPUs for a Windows workload and explain why more is often slower
- Select a cache mode deliberately, against the Proxmox default rather than around it
- Weigh the balloon driver against the documented warning about it
- Measure a Windows guest from both sides and reconcile the two answers
Prerequisites
Verified against Proxmox VE 9.2.4 · Proxmox Backup Server 4.2.5 · Ceph Squid / Tentacle · Debian 13 (Trixie) · Linux kernel 7.0 (PVE 9.2 default) · 2026-08-12
“The Windows VM is slow” is one of the least informative sentences in virtualisation, and it arrives more often than any other performance report. Part XVII gave you a general method for it. This lesson is about the parts that are specific to Windows guests — where the defaults are wrong, where the documented advice contradicts what people assume, and why the number inside the guest and the number on the host so often disagree.
Start from an uncomfortable fact: the majority of Windows performance tickets in a healthy estate are not caused by anything Windows-specific. They are caused by host contention, storage that is saturated by a different guest, or a vCPU count that somebody increased in an attempt to fix the first two. Confirm the host is healthy before you touch the guest, using Part XVII. Everything below assumes you have.
CPU: the default is portable, not fast
cputype defaults to kvm64, which the administration guide describes as a
model that “works on essentially all x86_64 host CPUs”. That is the whole
design goal — it is a lowest-common-denominator model chosen so a VM can live
migrate between a ten-year-old Xeon and a current EPYC without the guest
noticing.
What it costs is every instruction set extension added since roughly 2007. For a Windows guest that means the AES instructions BitLocker and TLS want, the vectorised paths that compression, media handling and modern .NET code paths take, and the population-count and bit-manipulation instructions that show up in hot loops all over the place. None of this produces an error; it produces software silently taking a slower path.
The other end is host:
If you want an exact match, you can set the CPU type to host in which case the VM will have exactly the same CPU flags as your host system.
with the documented caveat:
If you want to do a live migration of VMs between different hosts, your VM might end up on a new system with a different CPU type or a different microcode version.
The Proxmox best-practice pages for Windows Server 2022 and 2025 both recommend
host. For a homogeneous cluster that is straightforwardly right. For a mixed
cluster the honest choice is a named model that every node supports — the oldest
generation in the cluster — which gets you most of the instruction sets without
making migration a gamble. Part IX covers the model selection; the Windows-
specific point is only that kvm64 costs Windows more than it costs a
typical Linux guest, because more of the Windows userland is compiled to take
advantage of what it finds.
ostype is a performance setting
This is the third time Lesson 1’s ostype field appears, and here is why. The
qm.conf reference describes it as:
Specify guest operating system. This is used to enable special optimization/features for specific operating systems.
Optimisations, not labels. The KVM hypervisor can present Windows-specific
interfaces to a guest that recognises them, and Proxmox decides whether to do so
from the guest type. A related option corroborates this: --cpu carries a
hv-vendor-id sub-option, a custom Hyper-V identifier string — an option that
would have no reason to exist unless Windows guests were being given a
Hyper-V-compatible interface to talk to.
The practical instruction is unchanged and now has a third reason behind it: set
ostype correctly. A Windows guest declared other runs with generic settings
on a hypervisor that had better ones available.
vCPUs: more is frequently slower
The administration guide is relaxed about overcommit in aggregate:
It is perfectly safe if the overall number of cores of all your VMs is greater than the number of cores on the server […] However, Proxmox VE will prevent you from starting VMs with more virtual CPU cores than physically available.
That is a statement about the host. It is not permission to give a single guest more vCPUs than it can use, and on Windows the penalty for doing so is larger than intuition suggests.
The rules that hold up in practice:
- Start at 2 or 4 vCPUs and increase only with evidence. A guest that is 90% idle on four vCPUs will be 95% idle on eight and slightly slower.
- One socket unless you need NUMA.
--sockets 1 --cores 8, not--sockets 8 --cores 1. Multiple virtual sockets imply a NUMA topology to the guest, and Windows will schedule as if the topology were real. --numa 1only for guests large enough to span host NUMA nodes, and then with a topology that matches the host. The option defaults to0. A guest smaller than one host node gains nothing and inherits a scheduling constraint.- Windows Server licensing is per physical core on the host, not per vCPU — but many application licences are per vCPU. Check before you double a guest.
Storage: controller, cache, and one honest tradeoff
The controller is settled: virtio-scsi-single, from Lesson 1, with the system
disk on scsi0. “Single” gives each disk its own controller, which is what
makes iothread=1 meaningful — a dedicated I/O thread per disk rather than
contention on one.
Cache mode is where the documentation and the wiki appear to disagree, and it is worth resolving rather than picking one.
The administration guide describes the default:
No cache (default) […] the guest system will be notified that a write is complete when each block reaches the physical storage write queue
and calls it “a good balance between safety and speed”. The Windows best-practice wiki pages recommend Write back “for optimal performance”.
Both are correct, for different questions:
| Mode | Write is acknowledged when | Use when |
|---|---|---|
none (default) | it reaches the storage write queue, bypassing the host page cache | You want the documented balance and predictable behaviour under host failure |
writeback | it reaches the host page cache | Throughput matters more than the window of exposure, and the storage is not already caching |
writethrough | it is on stable storage, with reads cached on the host | Read-heavy guests where write latency is not the constraint |
directsync | it is on stable storage, no host caching either way | Databases with their own durability requirements, or clustered storage |
unsafe | immediately, ignoring guest flushes | Never on anything you would miss. Template builds and throwaway test guests only |
The remaining disk options are less contentious. discard=on lets the guest’s
TRIM reach thin-provisioned storage, which matters on ZFS, LVM-thin and Ceph;
ssd=1 presents the disk as non-rotational so Windows enables TRIM and disables
the defragmentation schedule it would otherwise apply to a “spinning” disk;
iothread=1 gives the disk its own thread. The aio sub-option accepts
io_uring, native or threads, and Part IX covers when to move off the
default.
The balloon driver: a documented tradeoff
Everything else in this lesson is a straightforward win. This one is not, and the administration guide says so without hedging:
For Windows OSes, the balloon driver needs to be added manually and can incur a slowdown of the guest, so we don’t recommend using it on critical systems.
Take that at face value. It is an unusual thing for a vendor to write about its own feature, and the reason is real: reclaiming memory from a Windows guest means the balloon driver allocates pages the guest then cannot use, and Windows responds to memory pressure by trimming working sets and paging — inside the guest, to a virtual disk, on shared storage.
The distinction that resolves this in practice is the one from Lesson 2:
- The balloon driver enables reclaim. This is the part with the performance cost, and it only has a cost when the host actually reclaims.
- The balloon service enables reporting. This has no performance cost at all, and it is what makes the Proxmox memory figure match Task Manager.
So the useful configuration for a critical Windows guest is: install both, and
set a floor that prevents reclaim. The balloon option is documented as the
“amount of target RAM for the VM in MiB”, and the driver is enabled by default
unless explicitly disabled with zero. Setting balloon equal to memory gives
you accurate reporting with no room for the host to take anything back.
VMID=9100
qm set "$VMID" --memory 16384 --balloon 16384
qm config "$VMID" | grep -E '^(memory|balloon):'For non-critical guests — test machines, seldom-used application servers, a desktop estate — a lower floor is a legitimate way to increase density, and the slowdown the guide warns about is a slowdown on a machine nobody is waiting for. Decide per guest, not per estate, and write the decision down.
Measuring, from both ends
A performance claim needs two measurements that agree. Guest-side numbers alone are unreliable under contention for the reason in the Under the Hood box: a guest cannot see time it did not get.
From the host, per-VM resource use over time:
# pvesh get /nodes/pve1/qemu/9100/rrddata --timeframe hour --output-format json | head -20[
{
"cpu" : 0.184,
"disk" : 0,
"diskread" : 45056,
"diskwrite" : 2686976,
"maxcpu" : 4,
"maxdisk" : 68719476736,
"maxmem" : 17179869184,
"mem" : 5837107200,
"netin" : 88192,
"netout" : 141312,
"time" : 1786000000
}
]Illustrative output
Two things to read: cpu is a fraction of the guest’s total vCPU capacity, so
0.184 on maxcpu: 4 is roughly three quarters of one core; and mem is only
meaningful if the balloon service is installed — otherwise it is close to
maxmem for the reasons in Lesson 2.
From the guest, the counters that answer the question the ticket is really asking. Processor time tells you almost nothing on its own; queue lengths and latency tell you where the wait is:
Get-Counter -Counter @(
'\Processor(_Total)\% Processor Time',
'\System\Processor Queue Length',
'\PhysicalDisk(_Total)\Avg. Disk sec/Read',
'\PhysicalDisk(_Total)\Avg. Disk sec/Write',
'\Memory\Available MBytes',
'\Memory\Pages/sec'
) -SampleInterval 1 -MaxSamples 60 |
Export-Counter -Path C:\perf\baseline.blg -ForceKey takeaways
kvm64is the portable default and costs a Windows guest every instruction set added since the mid-2000s. Usehoston a homogeneous cluster, or the oldest common named model on a mixed one.ostypeis documented as enabling special optimisations for specific operating systems. It is a performance setting, not a label.- More vCPUs is often slower. Lock-holder preemption scales with the number of contending vCPUs, and its signature is high guest CPU with no extra work done.
- Cache mode is a real decision with a real exposure.
writebackacknowledges writes into the host page cache; guest flushes are still honoured, so the loss window is unflushed data at the moment the host dies. - Proxmox does not recommend the Windows balloon driver on critical systems.
Install the balloon service everywhere for reporting, and set
balloonequal tomemoryon guests where reclaim would hurt. - Guest-observed latency includes time the vCPU was not running. Check the host before you believe a guest-side storage number.
Knowledge check
Knowledge check · 4 questions
Q1. A Windows application server on 8 vCPUs shows 80% processor time across all vCPUs in Task Manager while serving its usual request rate. Host CPU is moderate. What is the most likely explanation and the correct response?
Q2. You are asked to enable cache=writeback on a Windows guest that runs a transactional database, on the grounds that the Proxmox best-practice wiki recommends write back for Windows. What is the correct answer?
Q3. A Windows guest reports 40 ms average disk read latency while the host shows the underlying device answering in 2 ms and far from saturated. Which of these are genuine possible occupants of the gap? Select all that apply.
Q4. Because Proxmox does not recommend the Windows balloon driver on critical systems, the correct configuration for a critical Windows guest is to leave the balloon device and its service out entirely.
Passing score: 75%. Answers are checked in this browser.