Bali, Indonesia
Insights

VPS Feels Slow but top Shows Low CPU Usage? Check Steal Time First

November 3, 2026
0:00
A common assumption when a VPS feels slow goes like this: top shows the CPU mostly idle, no process uses more than a few percent, so the CPU can't be the problem. It must be the application, the database, or the disk. On bare metal that reasoning usually holds. On a virtual server it skips one number at the end of the CPU line in top: st, short for steal time. Steal time is the share of time your virtual CPU wanted to run but the hypervisor (the layer that runs many virtual machines on one physical host) gave the physical core to another guest instead. When steal is high, your server is waiting for CPU it never received, and the usual per-process numbers will not show it.
Inside a VM, the kernel only measures what happens while its vCPU is actually running on a physical core. If a process is ready to run, the guest scheduler hands it the vCPU, but the hypervisor may pause that vCPU because other guests on the same host need the core too. During that pause nothing in your guest runs. Your PHP worker isn't burning CPU, it's frozen in line. That's why the per-process %CPU column looks harmless. A process can only accumulate CPU time when it actually gets CPU, so a starved process shows low usage exactly because it's starved. Requests still take longer, page loads stretch out, cron jobs overrun, and SSH typing can feel laggy, while top suggests the box is relaxed. This is usually called the noisy neighbor problem. Hosts are often overcommitted, meaning more vCPUs are sold than physical cores exist. That's normal and usually fine because most guests are idle most of the time. It breaks down when several guests on the same host get busy at once.
In top, the second summary line splits CPU time into categories. The last field is steal.
TEXT
# Illustrative output format (numbers are made up)
%Cpu(s):  8.0 us,  3.0 sy,  0.0 ni, 54.0 id,  1.0 wa,  0.0 hi,  0.0 si, 34.0 st
Here us is user-space code, sy is kernel work, id is idle, wa is time waiting on I/O, and st is steal. Someone glancing at us and sy sees about 11% and moves on. The 34% steal says a third of the time your vCPUs wanted to run, they were not allowed to. Press 1 in top to see each vCPU separately, since steal often hits some cores more than others. top refreshes every few seconds by default, and steal tends to come in bursts. vmstat gives a cleaner rolling view:
BASH
# Read-only: one sample per second, ten samples
vmstat 1 10

# Illustrative output format (numbers are made up)
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
#  r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
#  6  0      0 812000  52000 940000    0    0     4    20  900 1500  9  3 50  1 37
#  5  0      0 811000  52000 940000    0    0     0    16  880 1450  8  3 52  0 37
Ignore the first line of vmstat output, it's an average since boot. Read the rest together. The r column is the number of processes runnable or running. If r stays above your vCPU count while st is high, work is queuing for CPU that the host isn't delivering. Depending on your procps version, there may be an extra guest-time column after st. One caveat: steal only shows up when the virtualization type reports it. Full VMs on KVM or Xen usually do. Container-based VPS plans may show st as 0 or a value that doesn't reflect your share. There, CPU limits are typically enforced with cgroups and show up as throttling instead, which is covered in Linux Kernel cgroups v2: How Modern Multi-Tenant Web Hosting Isolates CPU & Memory.
Steal is not the answer every time a VPS is slow. The point is to check it first, because it's cheap to read and it changes who needs to act. A rough way to sort the patterns: If st is high and r exceeds the vCPU count, the guest wants CPU and the host isn't providing it. Tuning the app further mostly moves the queue around. If st is low but wa is high and the b column (processes blocked, usually on I/O) is non-zero, the bottleneck is storage. That can still be a neighbor problem at the disk layer, but it's a different conversation and a different set of tools, such as iostat. If si and so (swap in and swap out) are non-zero during slowness, the server is short on memory and paging. CPU and disk numbers look odd as a side effect. If us is near 100% across all vCPUs and st is low, you are using the CPU you paid for. That's an application or sizing problem, and the provider has nothing to fix. If everything looks quiet, including low st, low wa, and an empty run queue, the slowness is probably a wait outside the CPU: database locks, a slow upstream API, DNS lookups, or PHP workers all busy waiting on something. Those need application-level tracing, not system counters. As a rule of thumb, a few percent of steal now and then is normal background on shared hosts. Sustained double-digit steal that lines up with user-visible slowness is worth escalating. These are not official thresholds, and what's tolerable depends on the workload. Latency-sensitive services feel steal sooner than batch jobs. On the support desk, this is where a lot of confusion comes from. The customer sees idle CPU, assumes the plugin or the database is at fault, and spends hours tuning things that were never the bottleneck. Reading one vmstat sample at the start usually saves that time.
A single screenshot of top with high steal is easy to dismiss as a momentary spike. What makes the case is steal measured over time and correlated with the slowdowns.
BASH
# Per-vCPU breakdown, 1-second interval, 5 samples (sysstat package)
mpstat -P ALL 1 5

# Today's CPU history, including %steal, if sysstat collection is enabled
sar -u

# Same for a previous day; file path and naming vary by distro
sar -u -f /var/log/sysstat/sa15
mpstat and sar both come from the sysstat package. sar only has history if the collector was already running, so install and enable it before you need it. The service setup and log directory differ between distros (Debian-based systems commonly use /var/log/sysstat, RHEL-based ones /var/log/sa). When you contact the provider, include the time windows when slowness happened, the %steal values from sar for those windows, and a short vmstat 1 capture taken during an incident. Also note the vCPU count. That turns "my server feels slow" into data the provider can match against host-side metrics. If you run the hypervisor yourself, for example on Proxmox, the same reasoning applies from the other side. High steal inside a guest means the host's physical cores are oversubscribed at that moment. Check host-level CPU load and which other guests are busy at the same time before touching the slow VM.
Since steal comes from outside your VM, fixes inside the guest only reduce how much you ask for. Caching, fewer PHP workers, or moving heavy cron jobs to quieter hours can soften the impact, but they don't give back stolen time. The real options sit with whoever runs the host: migrating your VM to a less loaded node, moving to a plan with dedicated or reserved CPU, or reducing overcommit on that host. Some plans deliberately cap CPU or use burst credits, and depending on implementation that can also show up as steal, so check your plan's terms first. Adding more vCPUs on the same contended host may not help. More vCPUs means more slots the hypervisor has to schedule onto busy cores, and the contention is still there. Whether it helps depends on the host and the provider's scheduling, so confirm before paying for a resize. The practical habit is simple: when a VPS feels slow and top looks quiet, read the st field and run vmstat 1 before touching the application. If steal is high and the run queue is backed up, collect sar history and take it to the provider. If steal is low, you've ruled out the host in under a minute and can move on to disk, memory, or the application with a clear conscience.