You've already ruled out CPU — top shows load that isn't crazy, cores aren't pegged, and yet the site still crawls, SSH feels sluggish, and MySQL queries that used to be instant now take seconds. That's the classic signature of disk I/O wait: your CPU is sitting idle, waiting on the disk to hand back data. It's one of the most misdiagnosed VPS slowdowns because every symptom looks like a CPU or memory problem until you actually check.

Symptom: Everything Feels Slow, But CPU Usage Looks Normal

High disk I/O wait usually shows up as:

  • Pages that hang for a second or two before loading, especially ones that hit the database.
  • MySQL queries stuck in "Sending data" or "Waiting for table level lock" for far longer than their query plan suggests.
  • SSH sessions that feel laggy even though top reports low CPU usage.
  • Cron jobs (backups, log rotation, mysqldump) that used to finish in minutes now taking hours.
  • A load average that's high, but when you check top, most processes are in "D" state (uninterruptible sleep) rather than actually burning CPU cycles.

That last point is the giveaway. A process in D state is blocked waiting on disk, not the CPU scheduler. If your load average is high but CPU% is low and you see a lot of D-state processes, you're looking at an I/O problem, not a compute problem.

Cause: What's Actually Driving the Disk Usage

Disk I/O bottlenecks on a VPS come from a handful of repeat offenders:

  • MySQL/MariaDB doing full table scans — a missing index turns a query that should touch a few rows into one that reads the entire table off disk.
  • Backup jobs running during peak hoursmysqldump, tar, or a backup plugin reading and compressing gigabytes of files while the site is under load.
  • Swap thrashing — if you're low on RAM, the kernel starts swapping to disk, and every memory access becomes a disk access. This is one of the most common causes and easy to miss if you're only watching CPU.
  • Log files growing unchecked — PHP error logs, Nginx access logs, or a misbehaving app logging in a loop can generate enormous write volume.
  • Noisy neighbours — on budget VPS plans with shared storage (especially older HDD-backed nodes), another tenant's heavy I/O can eat into your throughput even though your own processes are behaving.
  • Undersized disk tier for the workload — a database-heavy app on a plan built for a brochure site will always be I/O bound, no matter how much you tune it.

Fix: Confirm It, Then Find the Culprit

Start by confirming you actually have an I/O problem, then narrow down which process is causing it.

Step 1: Check %iowait with vmstat or top

Run:

vmstat 1 5

Look at the wa column under CPU. Anything consistently above 10-15% on a busy VPS is worth investigating; above 30% and you have a real bottleneck. You can also press 1 in top to see per-core stats, where the same wa figure shows up.

Step 2: Confirm which disk is saturated with iostat

iostat -xz 1 5

If iostat isn't installed: apt install sysstat (Debian/Ubuntu) or yum install sysstat (AlmaLinux/CentOS/Rocky). Watch the %util column — if it's consistently near 100% on your main disk, that device is your bottleneck. await (average time per I/O request in milliseconds) climbing into the hundreds is another red flag, especially on SSD-backed VPS plans where it should normally stay in single digits.

Step 3: Find the specific process

iotop -oPa

This lists only processes actively doing I/O, sorted by accumulated reads/writes. Install it with apt install iotop or yum install iotop, and run it with sudo. Nine times out of ten you'll see mysqld, a backup script, or a PHP-FPM worker stuck reading a huge file at the top of the list.

Step 4: Fix based on what you find

What iotop showsFix
mysqld constantly at the topCheck the slow query log for full table scans, add missing indexes, and consider bumping innodb_buffer_pool_size so more data is served from RAM instead of disk.
A backup script or tar/mysqldump jobMove it to off-peak hours via cron, and use nice -n 19 ionice -c2 -n7 in front of the command to lower its I/O priority so it doesn't starve everything else.
Swap usage climbing (check with free -h)Add RAM if you can, or reduce memory pressure — lower innodb_buffer_pool_size, cap PHP-FPM worker count, or disable unused services eating memory.
Rapidly growing log filesSet up logrotate for anything writing outside the standard system logs, and fix whatever's causing the excessive logging in the first place.
Everything looks reasonable but %util is still highYou're likely disk-tier limited. On SkyServer VPS plans this usually means it's time to move to an NVMe-backed plan or add a dedicated volume for the database.

Quick mitigation while you diagnose

If the site is actively struggling and you need breathing room right now, three things help immediately without touching config files:

  • Kill or pause any backup/export job currently running.
  • Enable or restart page caching (Redis, LiteSpeed Cache, or a plugin like WP Super Cache) so fewer requests hit MySQL and disk at all.
  • Restart mysqld if a runaway query has been stuck for a long time — check SHOW FULL PROCESSLIST; first and try killing just that thread with KILL <id>; before resorting to a full restart.

Prevention: Keep I/O From Creeping Back Up

  • Schedule backups for low-traffic hours and use ionice so they never compete with live traffic for disk bandwidth.
  • Review your slow query log weekly if you run a database-heavy app — catching a missing index early is a lot cheaper than discovering it during a traffic spike.
  • Set up basic monitoring (even a simple cron job emailing you when %iowait crosses a threshold) so you catch this before customers notice.
  • Right-size your storage for your actual workload — a database that's outgrown its disk tier will keep coming back to bite you no matter how much tuning you do.
  • Rotate and cap logs so a misbehaving script can't silently fill the disk with writes over weeks.

Frequently Asked Questions

How do I know if it's disk I/O and not just a slow database query?

Check vmstat 1 while the slowdown is happening. If the wa (I/O wait) column is elevated and processes show up in D state in top, it's I/O. A slow query alone, with fast disk, usually shows high CPU or high query time without the wa spike.

What's a normal %iowait value on a VPS?

Under 5% most of the time is healthy for a typical web/database workload. Brief spikes during backups are normal. Sustained readings above 15-20% during regular traffic mean the disk can't keep up with demand.

Will adding more RAM fix a disk I/O problem?

Often, yes — especially if the root cause is swapping or MySQL constantly re-reading data from disk because the buffer pool is too small. More RAM lets the OS and MySQL cache more in memory, reducing actual disk reads.

Is this different from the "disk full" or "disk quota exceeded" errors I've seen?

Yes. Disk full/quota errors are about running out of storage space and are usually obvious (writes fail outright). I/O wait is about the speed of read/write operations on a disk that still has plenty of free space — it's a throughput problem, not a capacity one.

Can a noisy neighbour on shared VPS hosting really cause this?

On older HDD-backed or heavily oversold nodes, yes. On SkyServer's NVMe-backed VPS plans this is far less common because storage I/O is much less contended, but it's still worth ruling out other causes first since it's usually something on your own end.