Which VPS Specs Actually Predict Performance (and the Five You Can Measure)

Cost per gigabyte of RAM is the wrong metric for a VPS, because the RAM itself is not what you are buying. What you are buying is a share of the host’s coherence domain: the last-level cache, the memory controller, and the physical socket the scheduler is willing to keep your vCPUs on. On a 2-socket host with 64+ cores, a 2-vCPU guest can be placed across a socket boundary and pay a cache-line transfer on every shared-memory access — a cost that never appears in top but shows up as a 30–50% throughput difference against an identical guest placed inside one NUMA node.

This page covers how to detect that, and which spec axes actually predict performance. For the plan-tier breakdown that maps these axes to price bands, see our VPS plan sizing guide by workload.

The Specs That Predict Nothing

Advertised specPredictive powerWhy
vCPU countLowShare of a core, a full core and a pinned core all read “2 vCPU”
RAM sizeMediumReal, but usually the easiest axis to satisfy
Disk sizeNoneUnrelated to IOPS, which depends on backing store and tier
“NVMe”LowLocal NVMe, network NVMe and tiered NVMe all use the same word
Bandwidth “unmetered”LowPort speed and traffic allowance are separate quantities
CPU model stringHighGeneration determines IPC, AVX-512 availability and memory speed
Guaranteed / dedicated CPUHighChanges the scheduler behaviour under host contention
Provider’s region and upstreamHighDetermines RTT to your users and whether the path is direct

Axis 1: Is the vCPU a Core or a Share?

Three products look identical on an order page. The difference shows up only under contention, so measure it by loading all your vCPUs and watching steal and frequency together.

# Load every vCPU for 120s
nproc --all
sudo apt install -y stress-ng sysstat
stress-ng --cpu $(nproc) --cpu-method matrixprod --timeout 120s &

# In another shell
vmstat 5 24 | awk 'NR>2{print $13,$14,$15,$16}'
grep -m1 'model name' /proc/cpuinfo; lscpu | grep -E 'MHz|Socket|NUMA'
Steal (st)us+syInterpretation
0–2%~200% on 2 vCPUDedicated cores or a very quiet host
3–8%~190%Oversubscribed but well managed — usually fine
10–25%variesActive contention; latency will be unpredictable
>25%variesNot a shared-core product you can run a database on

Steal is reported by the hypervisor, so it is trustworthy when it is high and uninformative when it is zero — some hypervisors simply do not expose it. Cross-check with /proc/stat on the guest: if the sum of user + system + idle does not add up to wall-clock time × vCPUs, the missing time is stolen regardless of what st says.

Axis 2: Single-Core Speed Is Usually the Binding Constraint

Most VPS workloads — PHP-FPM workers, MySQL query execution, Node.js event loops, Redis command processing — are single-threaded per request. Four slow cores beat two fast cores only when you have real concurrency; below roughly 50 requests per second, single-core speed dominates response time.

sudo sysbench cpu --threads=1 --cpu-max-prime=20000 --time=60 run | grep 'events per second'
sysbench cpu --threads=1 --cpu-max-prime=20000 --time=60 run | grep 'events per second'
Events/sec (1 thread, prime 20000)CPU classTypical price band
2,000–2,400Older Xeon E5 / EPYC 7002, often oversubscribedEntry, $5–8
2,600–3,200Xeon Scalable Gold, EPYC 7003Mid, $10–25
3,400–4,200Recent EPYC 9004 / 9005, Ryzen dedicatedUpper mid, $25–50
4,500+Dedicated Ryzen / current-gen with high clock$50+

Absolute numbers drift with sysbench version, so compare candidates using the same binary and the same prime count. A 40% gap between two “same price” plans on this test is a 40% gap in your application’s response time at low concurrency — a difference no amount of nginx tuning can recover.

Axis 3: Disk Is Not “Size” — It Is IOPS Tier and Backing Store

Ask three questions in this order. First, is the storage local to the hypervisor or is it a network block device? Local NVMe gives sub-0.2 ms p99 at queue depth 1; network-attached storage is typically 0.5–2 ms and far more variable. Second, is there a published IOPS cap and does it scale with disk size? Third — and most important — what does the p99 look like after ten minutes of sustained writes, when any burst credit has been spent?

sudo fio --name=rw --ioengine=libaio --direct=1 --bs=4k --iodepth=32 \
  --rw=randwrite --size=2G --runtime=600 --time_based \
  --write_iops_log=/tmp/fio --log_avg_msec=10000 --group_reporting

# Read the per-10s IOPS from the log to see burst credit expire
awk -F, '{print $2, $8}' /tmp/fio_iops.1.log | sort -k2 -n | head -5

A disk that reports 20,000 IOPS in the first ten seconds and 1,200 IOPS from minute three onward is sold on burst credit. That is not a defect — it is a product — but it means your database write path will behave completely differently during a bulk import than during normal traffic. Test with the workload you will actually run, and test it for at least ten minutes.

Axis 4: Does the Guest Span One NUMA Node?

Within a guest you rarely see the physical topology, but you can infer it. If lscpu reports two sockets or two NUMA nodes on a 2-vCPU instance, the guest has been split across a boundary. Memory-bound workloads then pay remote-node latency on every access.

lscpu | grep -E 'Socket|Core|Thread|NUMA node'
numactl --hardware 2>/dev/null | head -8
# Compare local vs remote memory bandwidth
numactl --cpunodebind=0 --membind=0 sysbench memory --memory-block-size=1M \
  --memory-total-size=4G --threads=1 run | grep 'transferred'
numactl --cpunodebind=0 --membind=1 sysbench memory --memory-block-size=1M \
  --memory-total-size=4G --threads=1 run | grep 'transferred'

A 15% or larger drop on the remote-node run means memory bandwidth is node-local and your workload is placed badly. You cannot fix this from inside the guest; it is a placement decision made by the provider. It is also a legitimate reason to ask for the instance to be re-scheduled, if the provider supports it.

Axis 5: Network Path, Not Just Port Speed

Two providers in the same city can differ by 30 ms of RTT to your users if one has a direct peer with their ISP and the other backhauls traffic to a transit exchange in another country. Measure from the VPS to the actual eyeball networks your users are on, not to a generic “speed test” node.

for t in 1.1.1.1 8.8.8.8 9.9.9.9; do
  echo "== $t"; mtr -rwzc 100 -i 0.2 $t | tail -1
done
# Number of distinct ASes on the path to your users
mtr -rwzc 50 -z  | awk '{print $(NF-1)}' | uniq | wc -l

Turning Axes Into a Purchase Decision

Score each candidate on the axes that matter for the workload, not on a single composite number. A rule that works for most small production stacks:

  • Static sites and small APIs — single-core speed first, latency to users second. vCPU count above two buys little.
  • Transactional databases — disk p99 and NUMA placement first, RAM second, core count third.
  • Build runners and batch jobs — sustained all-core CPU, and explicitly not burst-credit plans, because builds run long enough to spend the credit.
  • Proxies and VPN endpoints — NIC throughput and per-flow vs aggregate shaping, with CPU only needing to keep up with the policer.

Run all five tests inside the first hour of a monthly-billed trial, keep the numbers, and re-run them at the end of the first week. If the week-two numbers match the hour-one numbers, you have found a provider whose allocation is stable — which is worth more than a marginally better headline spec. Our VPS plan sizing guide by workload maps each of these axes to concrete plan requirements.

Leave a Reply