Troubleshooting High Load: Pinpointing CPU, Memory, and I/O Bottlenecks

When your server slows down or alerts fire, work through the resources in order — top, free, iostat, ss — to find which one is actually saturated.

When your server or VPS gets sluggish, times out, or trips a monitoring alert, resist the urge to reboot blindly. System resources come down to just four categories — CPU, memory, disk I/O, and network — and if you rule them out one at a time, you'll quickly land on the one that's actually maxed out. Here's a practical workflow.

Step 1: Read the load average

Start with uptime or top to see the load:

uptime
# 15:04:01 up 30 days,  load average: 3.20, 2.80, 1.90

Those three numbers are the average load over the last 1, 5, and 15 minutes. What matters is comparing them against your CPU core count:

nproc          # number of logical cores, e.g. 4

Rule of thumb: a load roughly equal to the core count means the box is busy but healthy; a load consistently above the core count (say a load of 8+ on a 4-core box) means work is queuing up. Note that load counts more than CPU — it also includes processes in uninterruptible sleep (the D state, usually waiting on I/O). So a high load isn't necessarily a CPU problem; keep digging.

Step 2: Is the CPU the bottleneck?

Use top (or the friendlier htop) and sort by CPU to find the culprit:

top          # press capital P to sort by CPU, press 1 to expand per-core
htop         # if installed, nicer UI; press F6 to pick a sort field

Pay close attention to the summary line at the top of top:

  • High us (user) → your application itself is crunching; drill into the process.
  • High sy (system) → heavy syscalls or context switching.
  • High wa (iowait) → the CPU is waiting on disk; don't blame the CPU, go check I/O.
  • High st (steal) → the hypervisor is handing your time slices to other VMs, a classic symptom on shared VPS instances.

Step 3: Memory and swap

Use free -h for the full memory picture:

free -h
#               total   used   free   shared  buff/cache  available
# Mem:           7.7Gi  5.1Gi  0.3Gi   0.2Gi       2.3Gi       2.2Gi

Watch available, not free — buff/cache can be reclaimed on demand. If available is low and Swap used keeps climbing, memory is tight and the system is paging, which drags everything down. Use vmstat to watch it move:

vmstat 1 5     # sample once a second, 5 times

Focus on the si/so (swap-in/swap-out) columns; anything persistently non-zero means active swapping. If a process gets killed, check the OOM log:

dmesg -T | grep -i -E 'killed process|out of memory'
journalctl -k | grep -i oom

An Out of memory: Killed process line means the OOM killer fired — time to add RAM or hunt down a memory leak.

Step 4: Disk I/O wait

When top shows a high wa, use iostat to zero in on the disk:

iostat -x 1 3    # needs the sysstat package: apt install sysstat

Look at %util (near 100% means the disk is pinned) and await (average time per I/O — high means slow). Then use iotop to see exactly which process is reading or writing:

sudo iotop -o    # -o shows only processes doing actual I/O

Step 5: Connections and network

If you suspect connections are piling up, count them with ss:

ss -s                          # summary of all connections
ss -tan | awk '{print $1}' | sort | uniq -c   # count by state
ss -tanp | grep :80            # connections and processes on a port

Lots of TIME-WAIT is usually fine; lots of CLOSE-WAIT typically means the app isn't closing connections properly.

Summary

Commit the order to memory: load vs. cores → top for CPU (us/sy/wa/st) → free/vmstat for memory and swap → iostat/iotop for disk → ss for connections. The trick is to first decide which class of resource is saturated, then follow the trail down to the specific process. A high load isn't always CPU, a high wa sends you to I/O, and exhausted memory gets processes OOM-killed. Nail the diagnosis first, and only then will you know whether to scale up, tune a setting, or fix the code.