How to Diagnose and Fix High CPU Usage on a VPS

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:

FieldMeaningIf it is high
usYour applicationsA process is genuinely busy — find it in Step 3
syThe kernelHeavy networking, many small file operations, or a misbehaving driver/process
waWaiting on diskNot a CPU problem — storage is the bottleneck
stSteal: time the hypervisor gave to other workMeasure 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

  1. nproc and uptime — is load above the core count?
  2. top or mpstat — is it us, sy, wa or st?
  3. ps aux --sort=-%cpu — which process?
  4. Recognise it? Check its logs. Don’t? Treat it as a possible compromise.
  5. Fix the cause, add limits, and confirm the load falls.

Continue Reading

Browse more articles in Troubleshooting & FAQ.

  • high cpu usage, cpu troubleshooting, load average, vps performance
  • 0 Users Found This Useful
Was this answer helpful?

Related Articles

Website Down? A Step-by-Step Troubleshooting Checklist

When a website goes down, working through checks in the right order saves critical time. This...

VPS Unreachable/Can't Connect via SSH: Troubleshooting Guide

Losing SSH access to your VPS is stressful, but most causes are fixable without needing to...

How to Fix "No Space Left on Device" Errors

A full disk can silently break databases, log writing, package installations, and web...

How to Diagnose and Fix Out of Memory (OOM) Errors

When a Linux server runs out of available RAM, the kernel's OOM (Out of Memory) killer forcibly...

How to Troubleshoot DNS Propagation Issues

DNS changes don't take effect everywhere instantly — caching at multiple levels means...