Guides

High load average: understand CPU usage before upgrading your server

A practical guide to reading load average, distinguishing between CPU saturation and disk I/O waits, and knowing when you actually need more power.

UptimeMag editorial team · 13 October 2026 · 7 min read

Real Estate

The quickest check

First things first: before reading on, open a shell and run

uptime

The output ends with three numbers, something like load average: 1.98, 2.15, 2.21. These are the average number of processes running or waiting (on CPU or disk) over the last 1, 5 and 15 minutes. Then count your cores:

grep processor /proc/cpuinfo | wc -l

Divide the first load average number by your core count. If the result is below 1, your server isn't overloaded, whatever the absolute number. A load average of 4 on an 8-core server means that load represents a workload using about half your available processing power. The same 4 on a single-core server tells a completely different story: on a single-CPU system, a load of 1.0 means the CPU is fully utilised; on a 4-CPU system, a load of 4.0 indicates full utilisation.

This holds on any Linux distribution, cPanel or Plesk included: the uptime command is identical, only where you go looking for the cause changes.

What load average actually measures

According to the uptime(1) man page, load average is the average number of processes in a runnable or uninterruptible state: a runnable process is either using the CPU or waiting to use it; an uninterruptible process is waiting for I/O access, for example disk. So the number alone doesn't tell you whether the bottleneck is CPU or disk—you find that out from the breakdown you see in top or htop.

A practical, unofficial but reasonable threshold to start worrying: under normal conditions you want load average to stay at 70% or less of your total CPU; if load average exceeds your available CPU count, the system is queueing requests to the CPU, which degrades performance.

The three cases that get confused

Run top (available everywhere) or htop if installed. The key line, under the header, is the one with %Cpu(s) percentages: us (user), sy (system), wa (iowait), id (idle).

Case What you see in top/htop What it means
CPU genuinely busy us and sy high, wa low code is running; needs optimisation or more CPU
Disk I/O waiting wa high, us/sy low the process is waiting for I/O, not computing
Too many requests together many processes in state R in column S, none individually heavy concurrency problem, not raw power

The distinction between the first two cases is also covered in the kernel documentation: %sys is time spent running kernel code, %wait is time spent waiting for I/O; %iowait is not an indication of how much I/O is actually in progress, only an indication of the extra time the system would show if I/O transfers weren't delaying code execution. In practice: high iowait with free CPU isn't fixed by buying more cores—it's fixed by understanding what the disk is waiting on (slow database, snapshot in progress, swap).

To isolate the third case, a high load average with CPU mostly idle and low wa, usually with many D or R state processes but each one light, is typical of too many PHP-FPM workers or too many Nginx connections queuing together, rather than one script doing heavy computation.

Linking the load to the site generating it (Ubuntu 24.04 with Nginx and PHP-FPM)

Here you need to move from "the server is slow" to "this site, this request". The PHP-FPM pool has a slow log that writes stack traces of requests slower than a threshold. You enable it in the pool file, for example /etc/php/8.3/fpm/pool.d/www.conf:

slowlog = /var/log/php8.3-fpm-slow.log
request_slowlog_timeout = 5s

With this configuration, the slow log records a stack trace for requests taking too long; any request exceeding the request_slowlog_timeout threshold has its PHP stack trace written to the slow log, showing exactly which function it was stuck in. After editing the pool file you need to reload the service:

sudo systemctl reload php8.3-fpm

Then check the slow log to see which script and function were blocking the worker:

tail -40 /var/log/php8.3-fpm-slow.log

The directive and its default are documented in the official PHP manual: request_slowlog_timeout is the timeout for serving a single request, after which a PHP backtrace is written to the slowlog file; the value 0 means disabled, and slowlog is the log file for slow requests, with default value #INSTALL_PREFIX#/log/php-fpm.log.slow, meaning no fixed path until you set it in the pool.

To get an overview of the pool as well as the slow log, enable the status page:

pm.status_path = /fpm-status

and query it with a local request (expose only from 127.0.0.1, not from the internet) to see how many workers are busy and whether the pool has hit pm.max_children.

On cPanel the equivalent is the MultiPHP INI Editor interface plus the PHP-FPM logs for the domain, which cPanel manages automatically under /var/log/php-fpm/; on Plesk the slow log is activated from the control panel in "PHP Settings" per domain, with the file written under the domain's log folder. In both cases the principle is the same as the manual pool: set a low threshold, wait for the problem to recur, read the stack trace.

For Nginx, if you suspect the load comes from too many requests queuing rather than PHP, the logging directive is documented in the official page: Default: error_log logs/error.log error; (the actual path on Ubuntu is usually /var/log/nginx/error.log, verifiable with grep error_log /etc/nginx/nginx.conf). There you see timeouts to PHP-FPM (upstream timed out), which confirms whether the bottleneck is upstream of the web server.

How to fix it, case by case

CPU genuinely busy (us/sy high). This isn't a web server configuration problem: it's a script, a query, a cron job consuming CPU. Identify the process with top sorted by CPU (press P in top/htop) and trace it to the PID, then to the owner (site or user) with:

ps -o user,cmd -p PID

If it's legitimate and recurring, the answer is to optimise the code or query, not necessarily buy more cores. If it's a runaway script (loop, stuck import), kill it and fix it.

Disk I/O waiting (wa high). Verify with:

iostat -x 1 5

(package sysstat, install with sudo apt install sysstat on Ubuntu if missing). If the disk's %util column is close to 100 while CPU is free, disk is the bottleneck: unindexed database, too many concurrent I/Os, or—on a VPS—provider's shared storage under pressure. In that last case the problem might not be your fault.

Too many requests together. Check the PHP-FPM status page: if active processes is always near pm.max_children, the pool is undersized for your actual traffic. Raise pm.max_children only if you have free RAM margin; otherwise reduce memory per worker or put a cache in front (static page, reverse proxy) so not every request reaches PHP.

When the problem might be the provider's

On a VPS, load average rising without a visible cause in your processes can stem from the shared physical node (CPU steal time, storage contended with other customers). Check steal time with top: in the %Cpu(s) line there's also an st column. If st is consistently above a few percentage points during load spikes, that's CPU your process never got because the physical host was giving it to others. In that case open a ticket with the provider attaching: precise time (with timezone) of the event, uptime output at the peak moment, top output with the %Cpu(s) line visible, and if possible iostat -x for the same interval. Without this data support can't distinguish a host-side problem from a client-side one.

How to verify it's fixed

After the intervention (query optimisation, worker increase, caching, or plan upgrade) redo the same starting measurement for the same traffic interval: uptime divided by core count below 1, wa back near zero if that was the problem, PHP-FPM slow log not filling up during peak hours. A single cold check isn't enough: recheck during the time when the problem was happening, otherwise you're only verifying the server is calm when nobody's on it.

When you actually need more power

More cores or CPUs makes sense only if normalised load average stays above 1 during regular traffic, with CPU actually full (us+sy high), and after you've already ruled out slow queries, missing cache and undersized PHP-FPM pool. If the problem is iowait, adding CPU changes nothing: you need faster storage or less I/O. If the problem is a single isolated spike (a backup, an import), don't change plans: move that operation outside traffic hours.

Written with the help of artificial intelligence and checked by the editors (EU AI Act, art. 50).