When to Upgrade Your VPS: Reading CPU, RAM and Disk Signals

A slow server does not always need a bigger plan. Sometimes one runaway process, a missing database index or a full log directory is the real problem. Other times the server is simply too small for the work you give it, and no tuning will fix that. This guide shows how to read the main Linux signals for CPU, memory and disk, so you can tell the two cases apart before you pay for more resources.
The commands work on common distributions such as Ubuntu, Debian, AlmaLinux and Rocky Linux. A few of them need the sysstat package, which provides iostat, mpstat, pidstat and sar.
# Debian / Ubuntu
sudo apt install sysstat
# AlmaLinux / Rocky Linux
sudo dnf install sysstat
Measure over time, not once
One look at top while a backup runs tells you nothing about a normal day. Check the server during typical traffic, during peaks and during scheduled jobs. If you enable sysstat collection with sudo systemctl enable --now sysstat (on Debian and Ubuntu also set ENABLED="true" in /etc/default/sysstat), sar keeps a history you can review later: sar -u for CPU, sar -r for memory and sar -d for disks. A week of history is a much better basis for an upgrade decision than a single bad afternoon.
CPU: compare load average with the number of cores
Load average is the average number of tasks that are running or waiting to run, over 1, 5 and 15 minutes. The number means little on its own. Compare it with the number of cores:
nproc
uptime
# 14:02:11 up 12 days, 3:41, 1 user, load average: 3.85, 3.62, 3.40
On a 4-core VPS, a 15-minute load of 3.4 means the CPU is busy but keeping up. As a rule of thumb, if the 15-minute load stays above the core count during normal hours, tasks are queuing and users will feel it. Short spikes above that line are normal.
There is a catch. On Linux, load average also counts tasks waiting on disk (the uninterruptible D state). A high load with idle CPU usually points to storage, not CPU. Check where the time actually goes:
mpstat -P ALL 5 3
If %usr and %sys are high on all cores, the CPU is the limit. If one core sits at 100% while the others are idle, the application is single-threaded: more cores will not help, but a faster CPU might. If %iowait is high, skip to the disk section below.
Steal time: when the problem is outside your server
On a virtual server, steal time (st in top and vmstat, %steal in mpstat) is the time your vCPU was ready to run while the hypervisor was serving another guest.
vmstat 5 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
5 0 0 412000 52000 980000 0 0 12 40 900 1500 55 10 20 1 14
Here st is 14, and the r column (tasks waiting for CPU) is above the core count. As a rule of thumb, steal that stays above 5–10% means you are not getting the CPU time your workload needs. Nothing inside the VM can fix that, and adding vCPUs on the same shared tier may not help either. A plan with dedicated vCPU usually does, because those cores are not shared with other guests.
Memory: read "available", then look for pressure
Linux uses spare RAM as page cache, so "used" memory always looks high. That is normal and healthy. Read the available column instead:
free -h
total used free shared buff/cache available
Mem: 3.8Gi 3.1Gi 120Mi 45Mi 620Mi 410Mi
Swap: 2.0Gi 1.4Gi 600Mi
Low available memory is a warning, not proof. Active swapping is the real problem. In vmstat 5, watch the si and so columns (swap in and swap out). Swap usage that stays flat is fine. Constant non-zero si and so mean the server keeps moving memory pages to disk and back, and everything slows down.
Kernels 4.20 and newer also expose pressure stall information (PSI), which shows the share of time tasks were stalled waiting for memory:
cat /proc/pressure/memory
some avg10=0.00 avg60=0.00 avg300=0.00 total=0
full avg10=0.00 avg60=0.00 avg300=0.00 total=0
As a rule of thumb, a some avg60 value that stays above about 10 during normal load means memory is a real bottleneck. The clearest signal of all is the OOM killer. When the kernel runs out of memory, it kills a process and logs it:
journalctl -k | grep -i "out of memory"
# Out of memory: Killed process 2314 (mysqld) total-vm:2457800kB, anon-rss:1203400kB ...
Before you upgrade, check the configuration. A database buffer pool, PHP-FPM pm.max_children or a worker count set too high for the available RAM will cause OOM kills on any plan size. If the settings are sensible and memory still runs out under normal load, the server needs more RAM.
Disk I/O: iowait and latency
%iowait is the time the CPU was idle because it was waiting for disk. It is a hint, not a measurement of the disk itself. To see the disk directly, use iostat:
iostat -x 5 3
Look at these columns for your main disk (often vda, sda or nvme0n1):
r_await and w_await: average time in milliseconds for reads and writes, including time in the queue. On NVMe storage, values that stay in the tens of milliseconds are a warning sign.
aqu-sz: average queue length. A queue that keeps growing means requests arrive faster than the disk can serve them.
%util: useful on a single spinning disk, but misleading on NVMe and virtual disks, which serve many requests in parallel. Do not decide on
%utilalone.
To find the process behind the I/O, run sudo iotop -o or pidstat -d 5. The cause is often a query without the right index, a log level left on debug, or a backup job running at peak time. Fix those first. If the I/O is legitimate and latency stays high, you need faster storage, or more RAM so more data stays in cache.
Disk space: fill level and growth rate
df -h
df -i
sudo du -xh --max-depth=1 / 2>/dev/null | sort -h | tail -n 15
df -i shows inodes. A disk can report free space and still fail with No space left on device when it runs out of inodes, usually because of millions of small cache or session files.
As a rule of thumb, plan when a filesystem passes 80% and act before it reaches 90%. Databases need free space for temporary tables and maintenance tasks, and a full disk can break writes in the middle of a transaction. Common places to reclaim space:
sudo journalctl --vacuum-size=500M
docker system df # shows what images, containers and volumes use
sudo apt clean # or: sudo dnf clean all
Then measure the growth rate. If usage grows by 2 GB a week and 10 GB are left, you have about five weeks. That number sets your upgrade date, not today's percentage.
Optimize or upgrade?
| Signal | Check first | Upgrade when |
|---|---|---|
| High load, high %usr | Slow queries, runaway processes, missing caching | Load stays above the core count at normal traffic after fixes |
| High steal time | Nothing inside the VM can fix it | Steal stays above 5–10%: move to dedicated vCPU |
| Swap in/out, OOM kills | Worker counts, buffer and cache sizes | Sensible settings still run out of RAM |
| High await and iowait | Indexes, log levels, backup timing | The I/O is legitimate and latency stays high |
| Disk above 80% | Logs, old backups, unused Docker images | The growth rate shows the disk will be full within weeks |
Upgrade with less risk
Take a fresh backup or snapshot before any resize.
Choose a quiet time. Many resizes need a reboot.
After a disk upgrade, check that the filesystem actually grew. Many cloud images do this on boot. If yours did not, extend the partition and the filesystem with the commands below.
Compare the same metrics with your baseline to confirm the upgrade solved the problem.
lsblk
sudo growpart /dev/vda 1
sudo resize2fs /dev/vda1 # ext4
# sudo xfs_growfs / # XFS
Device and partition names differ between servers, so confirm them with lsblk before you run anything.
Run it on IM HOST
If your checks point to a real resource limit, IM HOST Cloud VPS gives you dedicated vCPU and RAM that are not shared with other guests, and you can upgrade anytime as the workload grows. Plans start at $16/month with 4 dedicated vCPU, 8GB RAM and 80GB NVMe, and include a free automatic backup every 3 days. For smaller workloads, Linux VPS plans start at $5/month with full root access on KVM. Moving from another provider? Our free migration covers files, databases and email with no downtime, and our 24/7 engineers can review the numbers with you before you choose a plan.
More from our blog
Discover more practical guides and product insights from the IM Host team.
View all articles