top inside the guest shows a high wa value, and the customer is sure the disk is failing. Then you look at the Proxmox host and the storage is barely working: low latency, plenty of headroom, no other VM hammering it. When the guest is waiting on I/O but the host is not busy, one of the first things worth checking is whether the VM's own disk has a bandwidth or IOPS limit set on it.
This is a common blind spot at the support desk. Throttle limits are set once, often when the VM is created from a plan or template, and then nobody remembers they exist. The guest has no idea a limit is there. It only sees a disk that suddenly stops getting faster.
Why the host can look healthy while the guest waits
wa column in top or vmstat) is the share of time the CPU sat idle while at least one process was waiting for disk I/O to finish. It does not tell you why the I/O is slow, only that something is waiting on it.
Proxmox disk limits are enforced inside QEMU, the process that runs the VM, before requests ever reach the host's physical storage. When the guest sends more I/O than the limit allows, QEMU holds the extra requests in a queue and releases them at the allowed rate. From the host's point of view, the storage only ever receives that trimmed, steady stream, so host-side tools show a calm disk. From the guest's point of view, requests take longer and longer to complete, the queue inside the guest grows, and processes pile up waiting. That waiting is what shows up as iowait.
So the mismatch itself is a clue. A busy host with a slow guest points toward storage contention. A quiet host with a slow guest points toward something between the two, and the throttle is the most likely candidate.
First: read the limits actually set on the disk
101 with the VM ID.
BASH
qm config 101 | grep -E '^(scsi|virtio|sata|ide)[0-9]+:'scsi0: local-lvm:vm-101-disk-0,iops_wr=300,mbps_wr=40,size=50G.
The options you care about come in a few families. mbps_rd and mbps_wr cap read and write bandwidth in MB/s, while mbps caps both combined. iops_rd, iops_wr, and iops do the same for operations per second. Variants ending in _max are burst limits, which let the disk briefly exceed the normal cap before falling back to it. In the web UI, the same settings live under the VM's Hardware tab, by editing the disk and opening its Bandwidth section.
A few things to note while you are here. Limits are per disk, so a VM with a separate data disk can have different caps on each one. A disk line with no throttle options means no limit is set on that disk, and you can move on to other causes. And write down the numbers, because the next step is comparing them against reality.
Second: measure what the guest is actually pushing
iostat from the sysstat package gives per-device numbers every second:
BASH
iostat -xm 1 10sda for SCSI disks or vda for VirtIO block disks.
The columns that matter here are r/s and w/s (read and write operations per second), rMB/s and wMB/s (throughput), the await columns (average time each request took, in milliseconds), and the average queue size, shown as aqu-sz or avgqu-sz depending on the sysstat version. %util will often read near 100 when you hit a cap, but on virtual disks that column is not very reliable on its own, so do not lean on it.
If you want the bigger picture of where the CPU time is going alongside this, the approach in Reading vmstat to Confirm If High CPU Steal Is Slowing Your VPS pairs well with it, since the same vmstat output shows both wa and st.
Third: line the two up and read the pattern
iostat reports and look for a ceiling.
The clearest sign of a throttle is a flat line. Real disks under load wobble. Their throughput moves around from second to second. A throttled disk under sustained load tends to sit at almost exactly the same number every interval, and that number matches the configured limit. If mbps_wr is set to an illustrative 40 and wMB/s sits right around 40 for the whole sample while await climbs and the queue grows, you have your answer. Small differences are normal, partly because MB and MiB are not quite the same unit, so look for "pinned near the limit", not an exact match.
Check IOPS separately, because this is the one people miss. Many workloads, especially databases and anything doing lots of small random writes, hit the IOPS cap long before the bandwidth cap. Throughput can look tiny, a few MB/s, which makes it seem like the disk is barely in use. But if w/s is pinned at the iops_wr value, the disk is maxed out on operations. If only the combined iops or mbps option is set, add reads and writes together before comparing.
Burst limits produce a recognizable shape too. Throughput starts high for a short while, then drops to a steady lower level and stays there. Customers often describe this as "it's fast for a bit and then gets slow", which is a useful phrase to listen for.
If you want to confirm from the host side as well, the VM's Summary page in the Proxmox web UI has disk I/O graphs, and those usually show the same plateau. For per-device counters as QEMU sees them, qm monitor 101 opens the QEMU monitor, where info blockstats prints read and write totals. That is still read-only, but type carefully, since the monitor also accepts commands that change the running VM.
When the cap is not the answer
iostat for high await on the underlying device), a degraded RAID or ZFS pool, a guest workload doing synchronous writes that wait for every flush, or CPU steal making everything look slower than it is.
And if the guest is pinned at the limit, that does not automatically mean the limit is wrong. Throttles are often there on purpose, to stop one VM from starving its neighbors on shared storage. The honest finding to report is "this VM is hitting its configured disk cap", and then the conversation becomes whether the workload needs a bigger plan, whether the application can do less I/O, or whether the limit was set by mistake. Changing the limit is a policy decision, not a fix you slip in quietly. Depending on your Proxmox version, a change may apply live or show up as a pending change that needs a VM restart, so check the Hardware tab after editing.
In practice, the check takes a few minutes: read the disk line with qm config, run iostat -xm 1 inside the guest while it is slow, and look for a value that sits flat at the configured cap. A pinned number that matches the config, combined with a quiet host, is about as clear a signal as this kind of troubleshooting gets. If nothing lines up, you have ruled out the throttle cleanly and can move on to the host's storage with more confidence.