Reading a VPS Spec Sheet: What vCPU, Steal Time and IOPS Caps Actually Mean

A VPS product page is a compressed advertisement, not a specification. Two plans that both read “4 vCPU / 8 GB / 200 GB NVMe / 10 TB” can behave like different machines once real work touches them, because the numbers describe ceilings that are shared, throttled or burst-limited in ways the table never shows. Every claim below maps to a counter you can read from procfs, sysfs or one benchmark run.

vCPU is a share of a core, not a core

When a plan says “2 vCPU”, the provider is almost always selling two hyperthreads or two scheduling slots carved from a larger physical core. On KVM this appears to the guest as two CPUs and nproc reports 2, but the host may time-slice those vCPUs against other tenants. Single-thread performance is therefore a function of the physical CPU generation and the host’s current load, not of the count you bought.

lscpu | egrep 'Model name|Thread|Core|Socket|MHz|L3'
grep -m1 'model name' /proc/cpuinfo
cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list

A credible provider exposes steal time in /proc/stat. If it is always exactly zero while the machine feels slow, you are either on a container without accurate accounting or on a very idle host.

Steal time thresholds worth acting on

Steal time is the percentage of wall-clock time your vCPU was runnable but the host chose to run someone else. It is reported per CPU in USER_HZ ticks. A one-minute average under 2 percent is normal noise. Between 2 and 5 percent you will see p99 jitter without a throughput drop. Above 10 percent sustained, your benchmarks are no longer measuring your code.

# instantaneous per-CPU steal, 5 samples, 1s apart
for i in 1 2 3 4 5; do
  awk '/^cpu[0-9]/ {t=$2+$3+$4+$5+$6+$7+$8+$9; printf "%s steal=%.2f%%\n", $1, 100*$9/t}' /proc/stat
  sleep 1
done

For a rolling view use vmstat 1 and read the st column, or mpstat -P ALL 1. If steal is high on one vCPU and low on the others, you are pinned against a noisy neighbour on a specific core — attach that mpstat output to your ticket.

Burst versus sustained CPU: finding the ceiling

Many budget plans sell “burst” CPU: several times your fair share for short intervals while the host has spare capacity, then throttled to a baseline the spec sheet rarely states. If a compile slows down after 30 to 60 seconds, you have hit that ceiling rather than a thermal or memory limit.

Measure it with a fixed-length busy loop and record throughput per interval. A steady result means sustained allocation; a falling curve means bursting.

# 120s of integer work, one process per vCPU, one line per second
for c in $(seq 0 $(( $(nproc) - 1 ))); do
  taskset -c $c bash -c 'end=$((SECONDS+120)); n=0; while [ $SECONDS -lt $end ]; do n=$((n+1)); done; echo "cpu'$c' iterations=$n"' &
done; wait

Compare the first and last thirty seconds of a longer run. A drop greater than 25 percent indicates a CPU quota, and the point where it drops tells you how large the burst bucket is.

IOPS caps, burst buckets and queue depth

“Up to 50,000 IOPS” almost always means 4K random reads at queue depth 32 on a burst bucket, not 50,000 IOPS at depth 1. Sustained random writes on shared NVMe are typically an order of magnitude lower, enforced by a token bucket that refills at the sustained rate. When it empties, p99 latency jumps from roughly 300 µs to 15 ms while throughput plateaus. Run fio for 60 seconds and read the latency distribution, not the headline IOPS.

fio --name=randrw --rw=randwrite --bs=4k --iodepth=1 --numjobs=4 \
    --size=2G --runtime=60 --time_based --direct=1 \
    --ioengine=libaio --group_reporting
fio --name=randread --rw=randread --bs=4k --iodepth=32 --numjobs=4 \
    --size=2G --runtime=60 --time_based --direct=1 \
    --ioengine=libaio --group_reporting

Run the depth-1 job first — four jobs at depth 1 approximates a transactional database. If you cannot reach roughly 1,000 to 3,000 combined IOPS, the plan is unfit for write-heavy SQL regardless of the headline figure. Check the scheduler too: NVMe should show none.

Network caps and the difference between port speed and throughput

A “1 Gbps port” describes the link, not your allowance. Providers commonly shape egress with tc, so a single stream may top out at 100-300 Mbps while the interface negotiates 1 Gbps. Inbound is usually unshaped, which is why downloads look great and uploads disappoint.

ethtool eth0 | egrep 'Speed|Duplex|Link detected'
# single-stream egress test, 10 seconds
curl -o /dev/null -w '%{speed_download} bytes/s\n' \
  --max-time 10 https://speed.cloudflare.com/__down?bytes=200000000
# multi-stream to detect per-flow shaping
iperf3 -c iperf.example.net -P 8 -t 10

If one stream gives 250 Mbps and eight give 900 Mbps, you are per-flow shaped — which matters for large single-file transfers and replication between instances. If eight streams also cap at 250 Mbps, the cap is per-VM and parallelism will not help. Check the monthly allowance on the same test: some providers count only egress, some count both directions. For background on how these allocations differ between plans, see our notes on how VPS resources are provisioned.

Memory: guaranteed versus ballooned

RAM is the one resource where the spec sheet is honest about the total and silent about the guarantee. With KVM ballooning the host can reclaim pages from your guest under pressure; you see rising swap activity rather than a failure. With containers the limit is enforced by cgroup v2 and the OOM killer fires instead.

free -m
grep -E 'MemAvailable|CommitLimit|Committed_AS' /proc/meminfo
cat /sys/fs/cgroup/memory.max 2>/dev/null
vmstat 1 5

A 4 GB plan showing 3.8 GB is normal firmware overhead; 3.4 GB suggests a hypervisor reservation. Watch si and so under steady load for five minutes — sustained swap-in on a machine that is not itself swapping indicates host-level pressure.

A ten-minute verification checklist

Record a baseline on a new instance so future slowdowns can be attributed to a change rather than argued about. Save the output below into a dated file with the plan details.

  • CPU: model string, thread siblings, one-minute steal average.
  • Burst: 120-second per-core loop test, compare first and last 30 seconds.
  • Disk: fio at depth 1 and depth 32, record IOPS and p99 latency.
  • Network: single-stream and 8-stream throughput, note the ratio.
  • Memory: MemTotal versus advertised, swap-in rate at idle and under load.

Any number that fails these checks is worth a ticket in the first week, while you can still cancel. Revisit the baseline quarterly — hosts get fuller, and day-one steal time is not month-six steal time. If you are comparing tiers before you buy, the same commands show where the price steps in VPS plans stop buying real performance.

Leave a Reply