KVM performance optimization Linux: 7 proven tweaks

The first thing you notice when a KVM guest feels sluggish is that the host CPU is nearly idle. The bottleneck is almost never raw horsepower, it is virtualization overhead: the emulated devices, a misconfigured vCPU topology, or a memory balloon that refuses to give pages back. KVM performance optimization on Linux is mostly about removing that overhead, not adding resources. These are the seven tweaks I apply to every KVM host I manage, in order of impact.
- Key takeaways:
- Set the guest CPU model to
host-passthroughto stop QEMU from masking modern instructions. - Use
virtiofor disk and NIC in every modern guest, it beats emulatede1000/ideby a wide margin. - Enable
iothreadand pin it to a dedicated physical core for serious disk work. - Tune the I/O scheduler on the host;
noneis usually right for NVMe,mq-deadlinefor SATA.
Why KVM guests feel slow when the host is idle
KVM is a type-1 hypervisor built into the Linux kernel, but the default QEMU configuration favors compatibility over speed. Unless you change it, your guest boots with an emulated disk controller, an emulated network card and a CPU model from 2008. It works, but you pay a tax on every interrupt and every I/O operation. The tweaks below close most of that gap. If you run workloads that are sensitive to this, the right foundation is a Linux VPS where you control the kernel and the hypervisor settings yourself.
调整 vCPU 和内存参数能明显降低 KVM 虚拟化开销。
Tuning vCPU and memory parameters noticeably reduces KVM virtualization overhead.
Step 1 - Pin vCPUs to physical cores
By default QEMU threads can float across all host CPUs. On a busy host the scheduler migrates them constantly, which thrashes the L2/L3 caches and adds latency. Pinning fixes the vCPU threads to specific physical cores. The guest sees stable performance and the host cache behaves predictably.
Use virsh vcpupin for a quick test, or better, set it in the domain XML so it survives a reboot:
virsh vcpupin test-vm 0 2
virsh vcpupin test-vm 1 3
virsh vcpupin test-vm 2 4
virsh vcpupin test-vm 3 5
This pins vCPU 0 to physical core 2, vCPU 1 to core 3, and so on. Check the result with:
virsh vcpupin test-vm
You should see each vCPU mapped to the core you assigned. On a host with Hyper-Threading, pin to physical cores, not threads, unless you know the workload benefits from SMT. Leaving hyperthreads idle for the vCPUs is usually the safer choice.
Step 2 - Use host-passthrough CPU mode
QEMU's default CPU model masks a lot of instruction set extensions to keep the guest portable. That is a bad trade for production. Switch the CPU model to host-passthrough so the guest sees the full physical CPU including AES-NI, AVX2 and, on newer hosts, AVX-512. This alone can lift crypto and vector-heavy workloads by 20-40 percent.
Edit the domain XML and change the CPU block:
virsh edit test-vm
<cpu mode='host-passthrough' check='none'/>
Then restart the guest and confirm inside the VM:
grep -o 'avx2\|aes' /proc/cpuinfo | sort -u
You should see aes and avx2 in the output. One caveat: a guest started with passthrough mode cannot be live-migrated to a host with a different CPU. If you need migration, use host-model instead, it exposes a safe subset of the host CPU. For pinned VPS workloads that stay put, passthrough wins every time.
Step 3 - Switch to virtio for disk and network
This is the single highest-impact change you can make. Emulated devices like ide and e1000 trap into QEMU for every operation. Virtio is a paravirtualized device: the guest driver talks to the hypervisor directly, bypassing most of the emulation layer. Disk throughput often doubles and network latency drops noticeably.
Check what your guest currently uses:
virsh dumpxml test-vm | grep -E '<target|<model'
You want to see virtio as the model for both the disk and the NIC. If you see ide or e1000, edit the domain and replace them:
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='none' io='native'/>
<target dev='vda' bus='virtio'/>
</disk>
<interface type='bridge'>
<model type='virtio'/>
</interface>
Inside the guest, confirm the drivers loaded:
lsmod | grep virtio
You should see virtio_blk and virtio_net in the list. Modern distros ship these modules, so the switch is usually just a config change and a reboot. If you build custom kernels, make sure these are compiled in, not left out of the initramfs.
Step 4 - Tune the I/O scheduler on the host
The host I/O scheduler decides how requests to the physical disk are ordered. The old cfq scheduler is gone, and the default can still add latency for virtualized workloads. For NVMe drives, none is almost always the right choice because the drive firmware handles ordering. For SATA SSDs, mq-deadline gives a good balance.
Check the current scheduler per device:
cat /sys/block/nvme0n1/queue/scheduler
The active scheduler is shown in brackets. Change it at runtime:
echo none > /sys/block/nvme0n1/queue/scheduler
To make it permanent, add a udev rule so it survives reboot:
echo 'ACTION=="add|change", KERNEL=="nvme*", ATTR{queue/scheduler}="none"' > /etc/udev/rules.d/60-io-scheduler.rules
Verify after a reboot with the same cat command, the value in brackets should be none. This tweak matters most when multiple guests share the same physical disk. Each guest's virtio queue feeds into the host scheduler, so getting this right reduces overall latency spikes.
Step 5 - Dedicate an iothread for disk-heavy guests
By default, disk I/O for a guest is handled by the main QEMU thread, which also processes vCPU events and emulation. On a busy guest that single thread becomes a bottleneck. An iothread moves disk processing to a separate thread that you can pin to its own physical core, so disk I/O no longer competes with vCPU work.
Add an iothread to the domain and attach the disk to it:
virsh edit test-vm
<iothreads>1</iothreads>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' iothread='1'/>
<target dev='vda' bus='virtio'/>
</disk>
Pin the iothread to a physical core that no vCPU uses:
virsh iothreadpin test-vm 1 6
Confirm the pin is active:
virsh vcpupin test-vm
virsh iothreadinfo test-vm
You want the iothread on core 6 and the vCPUs on cores 2-5. This separation is what removes the contention. On a host with many disk-heavy guests, give each one its own iothread and its own core. This is one of the most effective KVM performance tweaks for database and file-server workloads.
Step 6 - Right-size the memory balloon
The KVM memory balloon lets the host reclaim memory from a guest, but it can also cause the guest to thrash if it balloons too aggressively. The default behavior often balloons the guest down when the host is under pressure, then the guest has to fault pages back in, which is slow. For production workloads, either disable the balloon or set a hard floor.
Check the current balloon setting:
virsh dumpxml test-vm | grep -A3 memballoon
To disable it, remove the memballoon device from the domain XML:
virsh edit test-vm
Remove this block entirely:
<memballoon model='virtio'>
<stats period='10'/>
</memballoon>
After a reboot, verify the balloon driver is gone from inside the guest:
ls /sys/devices/virtio-ports/
The virtio balloon port should not be listed. Disabling the balloon means the host can no longer overcommit memory, so make sure you size the host RAM properly. The trade-off is worth it: a guest that never gets its memory yanked away performs far more predictably.
Step 7 - Account for NUMA on multi-socket hosts
On a host with two or more physical CPUs, memory is split into NUMA nodes. A guest that spans both nodes pays a penalty every time it touches memory on the remote node. The fix is to keep the guest's vCPUs and memory on a single NUMA node. Libvirt can do this automatically if you let it know the host topology.
Check the host NUMA topology first:
numactl --hardware
You will see a list of nodes and which CPUs belong to each. Then edit the guest and pin it to one node:
virsh edit test-vm
<numatune>
<memory mode='strict' nodeset='0'/>
</numatune>
This forces the guest's memory to come from node 0. Pair it with vCPU pinning from Step 1, but only pin to cores within node 0. Inside the guest, verify it sees a single node:
numactl --hardware
The guest should report one node with all its memory. On a two-socket host, this keeps all memory accesses local and removes the cross-node penalty. Get this wrong and you can lose 20 percent of throughput on memory-bound workloads, so it pays to check even on smaller hosts. Many KVM VPS plans run on single-socket hosts where this is less of an issue, but it matters the moment you move to dedicated hardware. If you run on a dedicated server, NUMA tuning is non-negotiable.
How to measure the impact of these tweaks
Do not apply tweaks blindly. Measure before and after with the same workload. Inside the guest, run a quick CPU and disk benchmark:
sysbench cpu --threads=4 run
fio --name=test --rw=randread --bs=4k --size=1G --numjobs=4 --iodepth=32 --runtime=30
On the host, watch for steal time, which is the percentage of time the vCPU waited for the physical CPU:
top -1
Look at the %st column for each vCPU, it should be near zero after pinning and passthrough are in place. A steal time above 5 percent means the host is oversubscribed and no amount of guest tuning will fix it. The same applies on the network side, check for dropped packets on the virtio interface:
ip -s link show eth0
The RX errors and TX errors counters should be zero. If you see errors, the virtio queue depth might need adjusting, but that is a topic for another post. These verification steps are what separate a real KVM performance optimization from guesswork.
Which tweaks matter most for which workload
| Workload | Tweaks that matter most | Expected gain |
|---|---|---|
| Web server (nginx, PHP) | host-passthrough, virtio NIC | Lower latency, higher req/s |
| Database (PostgreSQL, MySQL) | iothread, virtio disk, I/O scheduler | Higher IOPS, lower latency |
| CI/CD runner | vCPU pinning, host-passthrough | Faster builds, less variance |
| Video encoding / heavy compute | host-passthrough (AVX2/AVX-512) | 20-40 percent faster encode |
| Memory cache (Redis) | NUMA pinning, disable balloon | Stable latency, no swap storms |
One more note. The balloon tweak and the NUMA tweak are about stability as much as speed. A guest that gets its memory reclaimed at the wrong moment will show latency spikes that have nothing to do with your application code. If you buy a VPS plan on shared infrastructure, the balloon is controlled by the provider, so check whether you have a fixed memory allocation. On self-managed hosts, apply the tweaks above and you remove the most common causes of inconsistent VM performance.
FAQ
Is host-passthrough safe for production KVM guests?
Yes, for guests that stay on the same host. The only risk is live migration to a host with a different CPU, which fails with passthrough mode. If you need migration, use host-model instead.
Why is virtio faster than e1000 or ide?
Emulated devices trap into QEMU for every operation. Virtio is paravirtualized: the guest driver and the hypervisor share memory directly, bypassing most emulation. Disk throughput typically doubles and network latency drops.
Should I disable the memory balloon on every guest?
Only if the host has enough RAM for the fixed allocation. Disabling the balloon prevents the host from reclaiming memory, which removes latency spikes, but it also removes overcommit capability.
What is steal time and why does it matter?
Steal time is the percentage of time a vCPU is runnable but had to wait for the physical CPU. High steal time means the host is oversubscribed. No guest tuning fixes that, you need a bigger host.
Do these tweaks work on all Linux distributions?
The libvirt and sysfs settings are distribution-agnostic. The commands work on Ubuntu, Debian, AlmaLinux and Rocky. The only difference is the package name for the tools, libvirt-bin on Debian/Ubuntu, libvirt-daemon on RHEL-based systems.
Related articles
- VPS performance benchmarking with fio, sysbench and iperf3
- How to benchmark NVMe disk speed on a VPS
- VPS server monitoring tools and strategies for 2026
- Why my VPS runs out of RAM and how to fix it
KVM 虚拟机性能优化要点
KVM 性能优化主要靠减少虚拟化开销,而不是增加资源。先把 CPU 模式改为 host-passthrough 并固定 vCPU 到物理核心,磁盘和网卡用 virtio 驱动,磁盘密集任务单独分配 iothread。宿主机 I/O 调度器对 NVMe 设为 none,SATA 用 mq-deadline。多路 CPU 服务器上注意 NUMA 绑定,生产环境建议关闭内存气球。每项调整都要用 sysbench、fio 和 top 验证效果,steal time 应接近零。


