MHOSTMHOST

High server load

If your VPS starts responding slowly, sites hang, and SSH barely reacts to input, high load is the likely cause. This article covers how to measure the load and figure out whether it's actually high for your particular server, how to find the specific process or cause behind it (CPU, memory, or disk), and which fix applies: throttling or killing a process, adding swap, tuning configuration — or moving to a plan with more resources.

What you'll need

  • SSH access as root or a user with sudo — all the commands below are run in the terminal.
  • Nice to have (not required) — access to the VMmanager 6 panel: useful for viewing CPU/RAM/disk graphs over a longer period, and for increasing server resources if needed. If you haven't logged in yet, see "How to log in to VMmanager 6".
Tip: work through the steps in order — from the big picture (load average) to a specific process, then to memory, the OOM killer, and disk. Each step narrows down the list of suspects. If you already know where to dig (say, dmesg immediately shows the OOM killer firing), feel free to jump straight to that step.

Step 1. Check load average and compare it to your CPU count

The fastest way to see how loaded the server is — the uptime command:

bash
uptime

Example output:

14:32:07 up 21 days, 3:12, 2 users, load average: 0.52, 0.58, 0.61

The three numbers after load average are the average length of the queue of processes that are either ready to run (state R) or waiting on disk I/O (state D), averaged over the last 1, 5, and 15 minutes. This is not a CPU usage percentage — it's the number of processes that, on average, didn't have a free core or disk available to them right at that moment.

On their own, these numbers mean nothing without knowing how many CPU cores the server has:

bash
nproc

Divide the load average by the result of nproc to get the average load per core:

  • All three numbers are around 1.0 per core or lower — the server is keeping up.
  • The 1-minute figure is noticeably higher than the 5- and 15-minute ones — a short spike (a cron job ran, a one-off burst of requests), usually nothing to worry about.
  • The 5-minute and especially the 15-minute figure is consistently above 1.0 per core — the load isn't a one-off: processes are genuinely queueing for CPU or disk.

You'll see the same three numbers in the header of top — a separate uptime is only useful for a quick glance without opening the interactive interface:

bash
top

By default, top shows CPU usage as a single combined %Cpu(s) line. Press 1 to switch to a separate line per core — that immediately shows whether the load is spread evenly or is actually pegged on just one core (a classic symptom of a single-threaded bottleneck, where the overall load average looks low across all cores but everything is really stuck on one of them).

Tip: since load average also counts processes in state D (waiting on disk), a high load average doesn't always mean CPU is the busy resource — the cause could just as well be disk (Step 5) or a memory shortage forcing the system into heavy swapping (Step 3).

Step 2. Find the process that's loading the server

top sorts processes by %CPU by default, so the heaviest process shows up right at the top:

bash
top

For a non-interactive snapshot — handy for copying or saving to a log:

bash
top -bn1 | head -20

If you'd rather have a friendlier interface, install htop:

Ubuntu / Debian:

bash
sudo apt install htop

AlmaLinux / Rocky (htop isn't in the default repositories — enable EPEL first):

bash
sudo dnf install -y epel-release
sudo dnf install -y htop
bash
htop

htop also sorts by %CPU by default, and shows a separate load meter for every core right at the top of the screen. The F6 key opens a menu to pick a different sort field (memory, for example), and F9 sends a signal to a process (including killing it) right from the list, without switching to another terminal.

Without an interactive UI, to sort by CPU or by memory separately:

bash
ps aux --sort=-%cpu | head -10
bash
ps aux --sort=-%mem | head -10

Of all the output columns, the ones that matter most are PID, %CPU, %MEM, and COMMAND. The STAT column uses the same state letters as the S column in top: R — running, D — waiting on disk I/O (if you see D next to a process near the top of the list, the likely cause is disk, not CPU — see Step 5), S — sleeping, Z — zombie.

The shape of the list itself tells you which way to go next:

  • One process clearly stands out in %CPU or %MEM compared to the rest — a runaway process, see Step 6, "One process has gotten out of control."
  • The load is spread across dozens of same-type processes (lots of php-fpm, node, or your web server's/app's own worker processes) — that's not one broken process, it's real load from traffic or an app configuration that isn't tuned for this server, see Step 6, "Legitimate load or poorly tuned software."
Done: once you've identified the PID and name of the offending process (if there's just one), keep it in mind — you'll need it in Step 6.

Step 3. Check memory and swap

Even if CPU looks fine, a shortage of RAM slows down the entire server — through heavy swapping.

bash
free -h

Example output:

               total        used        free      shared  buff/cache   available
Mem:            3.8Gi       2.9Gi       120Mi        45Mi       900Mi       650Mi
Swap:           2.0Gi       1.6Gi       412Mi

Look at the available column in the Mem row — it isn't literally "free" memory, it's an estimate of how much can actually be handed to new processes without swapping (accounting for page cache the kernel can reclaim). If available is low, and used in the Swap row is high and climbing, that's memory pressure.

Check whether swap is even configured on the server at all:

bash
swapon --show

Empty output means there's no swap at all — in that case, once RAM runs out, the kernel has only one option left, covered in Step 4.

Current swap activity is shown by vmstat:

bash
vmstat 1 5

The si and so columns show kilobytes per second read from and written to swap. Consistently non-zero values mean the server is actively swapping right now: what should be memory access is actually going through disk, which is orders of magnitude slower than RAM — that's exactly why the whole system feels sluggish, even if individual processes' %CPU doesn't look particularly high.

Done: if swap is actively in use and si/so aren't zero, move on to Step 4 to check whether the OOM killer has already fired, then to Step 6, "Not enough RAM."

Step 4. Check whether the OOM killer has fired

When RAM and swap run out completely, the Linux kernel doesn't just slow down — it forcibly terminates one of the processes to keep the system alive at all (the OOM killer, Out-Of-Memory killer). This explains sudden crashes of exactly the "heaviest" process (often a database or another memory-hungry service) with no error at all in that application's own logs.

bash
sudo dmesg -T | grep -Ei 'out of memory|killed process'

Example line from the output:

[Thu Jul  9 11:47:02 2026] Out of memory: Killed process 18342 (mysqld) total-vm:2145392kB, anon-rss:1583020kB, file-rss:0kB, shmem-rss:0kB, UID:112 pgtables:3456kB oom_score_adj:0
Verify: the exact set of fields in the line (total-vm, anon-rss, UID, pgtables, etc.) varies slightly by kernel version, but the phrase Out of memory: Killed process PID (name) itself is stable across all modern kernels and unambiguously points to the OOM killer firing.

dmesg's ring buffer is cleared on every reboot. To search for these events over a longer period, use the system journal — the same kernel messages, but kept across reboots:

bash
sudo journalctl -k --no-pager | grep -Ei 'out of memory|killed process'

If you find entries like this, it confirms that at that moment memory — not CPU or disk — was the bottleneck. Move on to Step 6, "Not enough RAM," or, if this keeps happening, straight to "Genuinely not enough resources — upgrade the plan."

Step 5. Check disk I/O

High load sometimes comes down not to CPU or memory, but to the disk itself — processes sit in the queue in state D, waiting for the disk to service a read or write.

The iostat utility ships as part of the sysstat package — it's in the default repositories on every target distro, no EPEL needed:

Ubuntu / Debian:

bash
sudo apt install sysstat

AlmaLinux / Rocky:

bash
sudo dnf install -y sysstat

Run it with extended statistics:

bash
iostat -xz 1 3

-x — extended statistics (including %util and await), -z — omit devices with no activity during the interval, 1 3 — three reports at a 1-second interval. The first iostat report always shows averages since the server booted — pay attention to the second and later reports, which reflect the actual current interval.

Example (trimmed — not all columns of the real output are shown):

Device            r/s     w/s   %util   await
vda              12.40   58.20   97.50   42.30
  • %util — the percentage of time during which the device had I/O requests outstanding. For a single disk, a value close to 100% almost always means the disk is the bottleneck. For a RAID array made up of several disks, this figure is less clear-cut — the device can serve several requests in parallel.
  • await — the average time to service a request, in milliseconds, including both time spent queued and time actually being serviced. On SSD/NVMe storage, a few milliseconds is normal; a sustained tens-to-hundreds of milliseconds is a clear sign the disk can't keep up.
Done: if %util is consistently close to 100% and await has clearly climbed, the bottleneck is disk, not CPU. See Step 6, "Legitimate load or poorly tuned software" (say, a database missing indexes, or a backup that landed during peak load) or "Genuinely not enough resources — upgrade the plan."

Step 6. Common causes and fixes

One process has gotten out of control

If Step 2 turned up a specific offending process, try stopping it cleanly first:

bash
kill -TERM 18342

If it's still alive a few seconds later, force it:

bash
kill -9 18342

If it's a service managed by systemd (a web server, a database, and so on), it's better not to kill the process directly but to restart the service itself — that way it comes back up in a clean state:

bash
sudo systemctl restart mysql

The actual service name may be different — for example, if it's a database, see "MySQL / MariaDB: install and configure" or "Redis: install and configure".

If you'd rather not kill the process and just make it less greedy, lower its CPU priority:

bash
sudo renice -n 15 -p 18342

The range runs from -20 (highest priority) to 19 (lowest). The process isn't terminated, but it yields to others when they're competing for CPU.

To hard-cap its share of CPU (say, no more than 50% of one core) without touching priority, use cpulimit:

Ubuntu / Debian:

bash
sudo apt install cpulimit

AlmaLinux / Rocky (needs EPEL — no need to add it again if you already did for htop):

bash
sudo dnf install -y epel-release
sudo dnf install -y cpulimit
bash
cpulimit -p 18342 -l 50

cpulimit runs in real time in the foreground. For the limit to keep working after you close your SSH session, run it in the background: cpulimit -p 18342 -l 50 &.

For a systemd service, it's more reliable to set this kind of limit through systemd itself — it survives the process restarting and doesn't need a separate utility running alongside it:

bash
sudo systemctl set-property mysql.service CPUQuota=50% MemoryMax=1G

This applies immediately, with no service restart, and persists across server reboots.

Not enough RAM

Important: before creating a swap file, make sure there's enough free disk space:
bash
df -h /

If space is tight or already gone, deal with "Disk is full" first.

Create a swap file (2G here is an example — size it to your server's memory and free disk space):

bash
sudo fallocate -l 2G /swapfile
Tip: if fallocate returns an error (this happens on some filesystems), use dd instead — it's slower, but always works:
bash
sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress

Lock down the file's permissions, initialize it as swap, and turn it on:

bash
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

For the swap file to be mounted automatically after a reboot, add an entry to /etc/fstab:

bash
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Verify the result:

bash
sudo swapon --show
free -h

You can also lower vm.swappiness — a kernel parameter (default 60, range 0200) that controls how eagerly the kernel reaches for swap instead of reclaiming the page cache:

bash
sudo sysctl vm.swappiness=10
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf
Important: swap isn't a substitute for missing RAM — it's a safety net against the OOM killer terminating processes outright. It's an order of magnitude slower than RAM, so consistently active swap almost always means "it's time to optimize or move to a plan with more RAM," not a normal steady state for the server.

Legitimate load or poorly tuned software

bash
crontab -l
bash
systemctl list-timers

If they line up, move the heavy jobs to a quieter time of day: "Scheduled backups with cron", "Incremental backups with restic", "Backups with rsync".

  • Database queries without indexes, N+1 queries, no caching — the most common cause of "traffic didn't really grow, but load did." At this point it's a question of the application's own code and configuration, not the VPS itself.

Genuinely not enough resources — upgrade the plan

If, after stopping or throttling stray processes and optimizing, the server is still consistently maxed out under load that's genuinely necessary, it's probably time to grow along with the project rather than keep fighting the symptoms.

  • Check the CPU/RAM/disk graphs for the last few days or weeks on the server's card in VMmanager 6 — if the load is consistently high rather than a one-off spike, that's the signal that your current plan doesn't have enough resources for your project.
📷 [screenshot: CPU/RAM load graph on the VPS card in VMmanager 6]
  • Upgrading vCPU, RAM, or disk is done from the my.mhost.ee customer portal (or through mHost support).

If the server looks compromised

Sometimes what looks like "load from traffic" has nothing to do with your application or with legitimate visitors at all — the server may have been compromised and put to work mining cryptocurrency, sending spam, or taking part in DDoS attacks. Signs that this is a compromise, not a configuration issue:

  • An unfamiliar name in the process list (Step 2) that you never installed — especially one running from /tmp, /var/tmp, or /dev/shm.
  • You kill a process (kill -9), and a few seconds later it's back — with the same PID or a different one.
  • Outbound network activity has spiked for no obvious reason:
bash
sudo ss -tnp
  • crontab -l or the files under /etc/cron.d/ contain jobs you definitely didn't add.
Important: if you spot any of this, don't spend time fine-tuning performance — this is now a security question, not an optimization one. Work through "Basic VPS security checklist". In genuinely bad cases, the most reliable option isn't to manually "clean up" a compromised system but to reinstall the OS from scratch through VMmanager (see "Reinstalling the OS and choosing a template") and restore only the data from backup, not the entire system environment.

What's next

If, after all these steps, the server still periodically bogs down specifically under peak but genuinely necessary load, the issue probably isn't a one-off fault — it's that your current plan needs to grow along with the project (see Step 6 above). Related topics: if load means the site doesn't just slow down but stops loading entirely, see "My website won't load"; if a service itself won't start rather than just eating resources, see "A service won't start"; if this looks more like running out of disk space than CPU/RAM, see "Disk is full".