When a VPS feels slow, CPU is the first suspect — but often the wrong one. This guide shows how to confirm CPU really is the bottleneck, find the process responsible, tell a real workload from a misbehaving one or a compromise, fix it, and decide when the answer is simply a bigger plan.
Step 1 — Read the Load Average Correctly
nproc
uptime
nproc prints how many cores the server has. uptime shows the load average over 1, 5 and 15 minutes. Load consistently above the number of cores means work is queueing. But Linux load counts processes waiting on disk as well as CPU, so a high load alone does not prove a CPU problem.
Step 2 — Split CPU Time into Its Parts
top
Look at the %Cpu(s) line:
| Field | Meaning | If it is high |
|---|---|---|
us | Your applications | A process is genuinely busy — find it in Step 3 |
sy | The kernel | Heavy networking, many small file operations, or a misbehaving driver/process |
wa | Waiting on disk | Not a CPU problem — storage is the bottleneck |
st | Steal: time the hypervisor gave to other work | Measure over time and tell us (see below) |
For a clearer view over time, install sysstat and run mpstat 5 6 (six samples, five seconds apart), or use vmstat 5.
Step 3 — Find the Process
ps aux --sort=-%cpu | head -15
top -c # press P to sort by CPU, c to show full commands
For a process you do not recognise, find out what it is and where it runs from:
ps -o pid,ppid,user,etime,cmd -p PID
sudo ls -l /proc/PID/exe
sudo ls -l /proc/PID/cwd
For a systemd service, see its logs: journalctl -u SERVICE -n 100. For a container: docker stats and docker logs CONTAINER.
Common Causes
An application stuck in a loop or crash-restarting
Its own logs usually show it. A service restarting every few seconds burns CPU on start-up alone: systemctl status SERVICE shows the restart count.
Expensive database queries
mysqld or postgres at the top of the list points at queries, not at the database server itself. Turn on the slow query log, find the worst queries, add indexes. For PostgreSQL, the pg_stat_statements extension ranks queries by total time.
Too many PHP-FPM or worker processes
Dozens of php-fpm workers all busy usually means slow requests piling up — often because of the database — or a pm.max_children set higher than the server can carry.
Overlapping cron jobs
A job that runs every minute but takes two starts piling up. Wrap it with flock so only one copy runs: * * * * * flock -n /tmp/job.lock /path/to/job.sh.
Bots and traffic floods
sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
sudo awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
The first lists the busiest client addresses, the second the busiest user agents. Rate limit or block abusive ones at Nginx; Fail2Ban can do it automatically.
A cryptominer or other compromise
Signs: a process near 100% CPU with a random or disguised name, running from /tmp, /dev/shm or a hidden directory, started by the web server user, or reappearing after you kill it. Check cron jobs (crontab -l -u www-data, /etc/cron.d/) and recent logins. Treat it as a compromise — see How to Recover from a Compromised VPS — and do not just kill the process.
Step 4 — Fix It
sudo systemctl restart SERVICE # a misbehaving service
docker restart CONTAINER # a stuck container
kill PID; sleep 5; kill -9 PID # stop a process, forcefully only if it ignores the first
Restarting buys time; the cause from the section above still needs fixing.
Step 5 — Stop One Thing Starving Everything Else
# systemd service: sudo systemctl edit SERVICE
[Service]
CPUQuota=150% # at most 1.5 cores
Nice=10
# docker compose
services:
worker:
cpus: "1.0"
Run batch jobs at low priority so interactive traffic wins: nice -n 19 ionice -c3 ./batch-job.sh.
About Steal Time
On a VPS, st shows CPU time your server was ready to use but did not get. Occasional small values are normal on any virtual server. If steal stays consistently high — several percent or more over long periods — collect a few minutes of mpstat 5 output and open a ticket with it so we can look at the host. CPU and RAM on our plans are allocated per server and not oversold; for workloads that need guaranteed resources, the VDS range has dedicated RAM.
When the Real Answer Is More CPU
If us is high, the process is a legitimate part of your workload, and it is already reasonably optimised, the server is simply too small for the job. Before upgrading, check that CPU is the actual limit — high wa points to storage, and swapping points to memory. High performance VPS walks through matching the bottleneck to the plan.
Checklist
nprocanduptime— is load above the core count?topormpstat— is itus,sy,waorst?ps aux --sort=-%cpu— which process?- Recognise it? Check its logs. Don’t? Treat it as a possible compromise.
- Fix the cause, add limits, and confirm the load falls.
Continue Reading
- How to Find and Stop Processes Using Too Many Resources
- How to Check VPS Resource Usage (CPU, RAM & Disk)
- How to Set Up Prometheus and Grafana for VPS Monitoring
Browse more articles in Troubleshooting & FAQ.