Fixing High CPU Usage on Linux Servers (Real-World Checklist)
Diagnose load averages, pinpoint runaway processes, and implement long-term guards with systemd and cgroups.
High CPU usage on a Linux server is one of those issues that sneaks up on you — everything looks fine until a monitoring alert fires or users start complaining about slow response times. This is the checklist I use every time I encounter elevated load averages in production.
Step 1: Check Load Averages
Start with the basics:
uptime
# 17:42:01 up 12 days, 3:14, 2 users, load average: 4.21, 3.89, 3.45
A load average above the number of CPU cores means your system is consistently overloaded. Check core count:
nproc
# 4
In this case, a load of 4.21 on a 4-core machine means it's at capacity.
Step 2: Identify Top Processes
top -b -n 1 | head -30
Or for a cleaner snapshot:
ps aux --sort=-%cpu | head -20
Look for processes with consistent %CPU above 50%. Don't panic at single spikes — look for sustained usage.
Step 3: Check for Runaway Processes
A runaway process is often a script with an infinite loop, a zombie worker, or a PHP-FPM child that didn't die cleanly:
# Find processes in uninterruptible sleep (D state)
ps aux | awk '{if ($8 == "D") print $0}'
Processes stuck in D state are usually waiting on I/O — check disk health too.
Step 4: Check systemd Service Status
If the culprit is a system service:
systemctl status nginx php8.2-fpm mysql
journalctl -xe -u php8.2-fpm --since "1 hour ago"
Step 5: Apply cgroups CPU Limits
To prevent a single service from consuming all CPU:
# Create a cgroup slice for your web services
systemctl set-property php8.2-fpm.service CPUQuota=150%
systemctl set-property mysql.service CPUQuota=200%
This limits PHP-FPM to 1.5 cores and MySQL to 2 cores, leaving headroom for the OS.
Step 6: Long-Term Guard with systemd
Add CPU accounting to your service units:
[Service]
CPUAccounting=yes
CPUQuota=150%
MemoryAccounting=yes
MemoryMax=512M
Outcome
After applying these steps on a recent client server, we reduced average CPU from 85% to under 40% during peak traffic. The primary culprit was an uncapped PHP-FPM worker pool spawning too many children under load.
Always profile before limiting. The goal is guardrails, not starvation.
