The most dangerous thing about a full disk on a WordPress server is that it never announces itself as a full disk. It announces itself as a database error, a failed upload, a site that's simply gone — and if you chase those symptoms individually, you'll fix nothing and the crash will come back on schedule. That's exactly what happened to me: my WordPress sites on an AWS EC2 instance went down repeatedly over several weeks, uptime alerts firing in the middle of the night, and the root cause every single time was the same line in df -h: the root volume at 100%.
Here's the full incident — diagnosis, the actual root cause (which wasn't what I expected), the cleanup that brought the sites back, and the two pieces of automation that have kept this class of crash from ever recurring.

What 100% disk usage actually looks like from the outside
When a Linux server exhausts its disk, everything that writes starts failing at once, and each service fails with its own vocabulary:
- WordPress throws "error establishing a database connection" — because MySQL can't write, not because MySQL is misconfigured
- Pages come back blank or half-rendered as PHP fails to write session and cache files
- Uploads die with permission-looking errors that have nothing to do with permissions
- Logs fill with "No space left on device" — right up until logging itself stops
- In the worst case, even SSH gets weird: you can log in but can't save a file
The diagnostic reflex worth burning into your fingers: any of those symptoms on a previously healthy site gets an immediate df -h. If / or /dev/xvda1 shows 100%, stop chasing the symptom — you've found the disease.
There's also an earlier warning window that I missed and now watch for. Disk exhaustion telegraphs itself in order of write size: scheduled backups fail first (biggest writes), then media uploads, then cache writes. If your backup plugin has been silently failing for a week, you are not watching a backup problem — you're watching a fuel gauge.
Finding the disk hogs
With SSH access, two commands localize the problem in under a minute:
df -h
sudo du -h --max-depth=1 / | sort -hr | head -20
sudo du -h --max-depth=1 /var/www | sort -hr | head -20
Walk the tree with du one level at a time, following the biggest directory down. On my server the trail led to /var/www/temp — and to the actual root cause.
Over 14GB of stale backup archives. An automated backup process had been dutifully writing .zip and .tgz files to a temp directory for months, and nothing had ever deleted one. Every few weeks the pileup crossed the line, the disk hit 100%, the sites crashed, and whoever was firefighting (me) deleted a few obvious files, got the sites up, and moved on — leaving the generator running. That's why the crash repeated: the cleanup treated the symptom, and the backup job was the disease.
The secondary offenders were familiar ones: bloated WordPress cache directories on a couple of sites, and error logs that had been quietly appending for a year. On a multi-site server, a few gigabytes here and there compound fast.
The cleanup that brought it back
The commands I actually ran, in order of payoff:
sudo rm -rf /var/www/temp/* # the 14GB of dead backups
sudo rm -rf /var/log/*.gz /var/log/*.1 # rotated, compressed old logs
sudo journalctl --vacuum-time=3d # systemd journal, capped to 3 days
sudo rm -rf /tmp/*
Then verify: df -h dropped from 100% to 72% immediately, and the sites came back.
Two hard-won caveats before you paste those into your own terminal:
Deleting an open log file frees nothing. If a process still holds the file handle, the space stays allocated until the process restarts. If df doesn't budge after a big delete, run sudo lsof +L1 to find deleted-but-open files, then restart the offending service. This one cost me twenty confused minutes the first time I hit it.
Restart MySQL deliberately after a full-disk incident. MySQL that hit a wall mid-write can be in a wounded state even after space returns. Restart it and then actually read the error log — InnoDB will tell you whether it recovered cleanly:
sudo systemctl restart mysql
sudo systemctl restart nginx
sudo tail -50 /var/log/mysql/error.log
If InnoDB reports crash recovery, verify your most important tables before declaring victory. A disk-full crash during a write is one of the few mundane events that can genuinely corrupt data.
The automation that made it permanent
Freeing space bought me weeks. Three pieces of automation ended the cycle, and together they took maybe half an hour to set up.
1. CloudWatch disk alarms — the one I'm most embarrassed I didn't have. EC2 doesn't report disk usage out of the box; you need the CloudWatch agent publishing the disk_used_percent metric. I set alarms at 75%, 85%, and 95%, notifying email. The alarm converts the failure mode from "sites down at 3 AM" into "calm ticket on Tuesday," which is the entire difference between ops and firefighting.
2. Snapshot backups with a retention policy, off the local disk. The original sin was backups accumulating on the same volume they were protecting — which is also useless as disaster recovery, since the backup dies with the disk. I moved to AWS Data Lifecycle Manager for weekly EBS snapshots with automatic expiry of old ones, and pointed WordPress-level backups at S3 instead of the local filesystem. If you're offloading media too, WP Offload Media with S3 takes the uploads directory out of the equation entirely.
3. A cleanup cron for anything that still lands locally:
# weekly: purge the temp directory the backup job writes through
0 3 * * 0 rm -rf /var/www/temp/*
Plus standard logrotate for the system side. The rule I took from the whole incident: every automated process that writes to disk needs a paired process that deletes. A backup job without a retention policy isn't a backup strategy — it's a slow-motion outage.
When cleanup isn't enough: grow the volume
Sometimes the disk is full of things you legitimately need, and no amount of pruning fixes an undersized volume. That's a different playbook — resizing the EBS volume and growing the filesystem live — and I wrote it up separately in my guide to fixing a full disk by increasing the EBS volume. The decision rule I use: if you're above 80% after an honest cleanup, or if growth is organic (real media, real database), resize. If you're above 80% because of logs, caches, and backups, resizing just buys the disease a bigger host.
It's worth saying that disk exhaustion is only one of the ways WordPress dies with a misleading error message on the screen — I walked through another full diagnosis in our "critical error on your website" case study, and the method is the same: ignore the symptom's wording, find the layer that's actually failing.
The checklist I'd hand my past self
- Any weird WordPress error on a healthy site →
df -hfirst, always. It costs three seconds. du -h --max-depth=1, walked downward, finds the hog in a minute.- Deleted a big file and
dfdidn't move →lsof +L1, restart the holder. - After any full-disk incident, restart MySQL and read the InnoDB log — don't assume.
- CloudWatch agent + alarms at 75/85/95 on
disk_used_percent. Non-negotiable. - Backups go off the volume (S3, snapshots), with retention policies. Local backup archives are a countdown timer.
- Every writer gets a deleter: cron cleanup or logrotate for anything that appends forever.
I've run WordPress on AWS for clients for 8+ years, and this incident class is the single most common "mystery outage" I get called into — and the most preventable. If your server is crashing on a schedule and the errors don't make sense, the disk is where I'd look first, and I'm happy to look at it with you.