How to Fix “503 Service Unavailable” and “508 Resource Limit Is Reached”
Your account or server ran out of PHP workers, processes or CPU. Find which limit was hit, find the URLs and bots eating it, then cache, block or upgrade.
Uploads fail, updates say “Could not create directory”, the database refuses writes or mail bounces. Find what is eating disk space or inodes and clear it safely.
Things start failing in odd, unrelated ways. Media uploads stop with “Unable to create directory wp-content/uploads/YYYY/MM. Is its parent directory writable by the server?”. Plugin updates say “Could not create directory.” or “Could not copy files. You may have run out of disk space.”. Orders or form entries do not save. Email to your domain bounces with “mailbox full” or “quota exceeded”. Then, sometimes, the whole site goes down.
All of these are the same root problem: the server, or your account’s slice of it, has run out of room to write. That can mean out of bytes (disk space) or out of inodes (the count of files and folders). This guide is for site owners, designers and freelancers on shared hosting or a VPS who need to find what filled the space, clear it without breaking anything, and stop it happening again.
A filesystem tracks two separate budgets. Bytes are the space your files take up. Inodes are entries in the filesystem’s index: every file, folder and symlink uses one, whatever its size. A million tiny cache files can exhaust your inode quota while using hardly any space, and one huge backup archive can fill the disk while using a single inode.
Shared hosting adds a quota per account on top of the physical disk. When you hit either quota, the operating system refuses new writes with the error “No space left on device” or “Disk quota exceeded”, and every program that tries to write reports it in its own words:
| What fails | What you see |
|---|---|
| Media uploads | “Unable to create directory … Is its parent directory writable by the server?” or “The uploaded file could not be moved to …” |
| Core, plugin and theme updates | “Could not create directory.”, “Could not copy files. You may have run out of disk space.” or “There is not enough free disk space to complete the update.” |
| Database writes | MySQL or MariaDB errors such as “Errcode: 28 - No space left on device” or “The table ‘wp_options’ is full”; on a VPS the database may stop and WordPress shows “Error establishing a database connection” |
| Incoming mail bounces with a mailbox-full or over-quota reply; outgoing mail queues and fails | |
| PHP sessions and temp files | Warnings like “session_start(): … No space left on device (28)”; logins, carts and checkouts fail |
| Caching and logs | Cache plugins report they cannot write; pages render without cache or with errors |
“Unable to create directory” and “Could not create directory” can also mean wrong file permissions. If the disk check in Step 1 shows plenty of room, our 403 and permissions guide is the better fit.
You need your hosting panel and, ideally, SSH. Everything in Steps 1 and 2 is read-only, so it is safe. Deleting files is not reversible, so the rule for this guide is: look, list, then delete, and never delete something you cannot identify. If a recent backup exists off the server, note where it is before you start removing local copies.
How to run the commands in this guide. Server commands run in an SSH terminal. Most start from your home directory or your site folder, the one that contains wp-config.php (often public_html); each command says which. WP-CLI is called wp on most servers; some hosts name it wp-cli. On a full disk, some commands may print errors about writing temp files; the read-only ones in this guide still work.
Most panels show disk usage and file count (inodes) on the main dashboard or a “Disk Usage” or “Statistics” page. Note both numbers against their limits. If you are on shared hosting and the panel shows usage at the limit, the quota is your problem even if the physical disk has room.
df -h
df -i
quota -s
Run these over SSH from anywhere. There are no placeholders. df -h shows space per filesystem in human-readable units (-h), and df -i shows inodes per filesystem. quota -s shows your account quota on servers that use filesystem quotas; on many shared hosts it prints nothing or “command not found”, which is fine, the panel figure is the real one.
How to read it: look at the Use% column (or IUse% for inodes) for the filesystem mounted at /, /home or wherever your site lives. 100% means writes are failing now. Above 90% means you are close. If df -h shows space but df -i shows 100%, you are hunting many small files, not big ones.
cd ~
du -xh --max-depth=1 2>/dev/null | sort -rh | head -20
Run this over SSH, starting in your home directory (cd ~). du totals the space under each folder; -x stays on one filesystem; -h prints sizes like 4.2G; --max-depth=1 shows only the immediate subfolders; 2>/dev/null hides “permission denied” noise. sort -rh orders human-readable sizes largest first and head -20 keeps the top 20.
The output is a list like 18G ./public_html and 6.1G ./mail. The last line, ., is the total. Move into the largest folder with cd and run the du line again. Repeat until you reach the folder that holds the bulk. On macOS’s BSD tools the flag is -d 1 instead of --max-depth=1, but you will be running this on the server.
ncdu -x ~
Run this over SSH if ncdu is installed (many hosts include it; on your own server, install it with your package manager). It scans the folder and opens a browsable list sorted by size. -x stays on one filesystem. Use the arrow keys and Enter to drill down, q to quit. Avoid its d (delete) key until you have finished Step 3 and know what you are removing.
find ~ -xdev -type f -size +SIZE -exec ls -lh {} + 2>/dev/null | sort -k5 -rh | head -30
Run this over SSH. SIZE is a threshold with a unit, for example 100M or 1G. -xdev stays on one filesystem, -type f matches files only, and -size +SIZE keeps those larger than the threshold. ls -lh prints each with its size, and the sort puts the largest first. Typical findings are .zip, .tar.gz, .sql, .wpress and .log files.
cd ~
find . -xdev | cut -d/ -f2 | sort | uniq -c | sort -rn | head -20
Run this over SSH from your home directory. find . lists every file and folder below the current one; cut -d/ -f2 keeps only the top-level folder name from each path; uniq -c counts how many entries fall under each; sort -rn puts the largest count first. Output looks like 812004 public_html and 95310 mail. Change into the top folder and run it again to drill down. On newer systems, du --inodes -x --max-depth=1 | sort -rn gives the same answer more quickly.
Once you know the heavy folder, it is almost always one of these.
This is one of the most common causes. Backup plugins write archives inside wp-content (for example wp-content/updraft for UpdraftPlus and wp-content/ai1wm-backups for All-in-One WP Migration), and a schedule with no retention limit fills the disk week by week. Manual backups made before a migration and forgotten (backup.zip, site-old.tar.gz, db.sql in the web root) are just as common, and they are also a security risk because anyone who guesses the name can download them.
wp-content/debug.log grows without limit while WP_DEBUG_LOG is on, and a plugin throwing a warning on every request can add gigabytes a day. Also check for error_log files in many folders (some servers create one per directory that hits a PHP error) and for plugin logs inside wp-content/uploads.
Page cache and minified asset folders such as wp-content/cache or a plugin’s own folder can hold hundreds of thousands of files. These are the usual inode culprit. They are safe to empty because they rebuild themselves.
WordPress unpacks updates into wp-content/upgrade and, since version 6.3, keeps a copy of the previous plugin or theme version in wp-content/upgrade-temp-backup while an update runs. Failed or interrupted updates can leave these behind. Old copies of whole sites (public_html/old, public_html_backup) are another common find.
Mailboxes count against the same account quota on most shared hosting. A catch-all address, years of Sent items, or a full Spam or Trash folder can run to many gigabytes. Mail usually lives under ~/mail.
MySQL and MariaDB can keep binary logs (files named like mysql-bin.NNNNNN or binlog.NNNNNN, usually under /var/lib/mysql). They record every change for replication and point-in-time recovery, and without an expiry setting they grow forever.
A node_modules folder in a theme or plugin can contain tens of thousands of files, all useless on a production site. So can a .git folder with a long history, or a vendor folder of test files.
Start with what is safest and gives back the most. Preview every deletion first: run the command with -print (or ls) instead of -delete and read the list. Only when the list contains exactly what you expect, run the delete.
If the disk is at 100%, empty the active debug log rather than deleting it, so any process holding it open does not keep the space:
ls -lh wp-content/debug.log
: > wp-content/debug.log
Run these over SSH from the site folder. The first shows the file’s size. The second truncates the file to zero bytes (the : command outputs nothing, and > overwrites the file with that nothing). Good: ls -lh afterward shows size 0. Then turn off WP_DEBUG_LOG in wp-config.php so it does not refill.
find ~ -xdev -type f \( -name "*.zip" -o -name "*.tar.gz" -o -name "*.sql" -o -name "*.sql.gz" -o -name "*.wpress" \) -size +SIZE -mtime +DAYS -print
find ~ -xdev -type f \( -name "*.zip" -o -name "*.tar.gz" -o -name "*.sql" -o -name "*.sql.gz" -o -name "*.wpress" \) -size +SIZE -mtime +DAYS -delete
Run these over SSH. SIZE is a minimum size like 50M so small legitimate zips are not caught; DAYS is a minimum age, for example 14. The \( and \) group the name patterns joined by -o (or). The first line only prints the matches. Read that list carefully, copy any archive you need to keep to your own computer or cloud storage, and only then run the second line, which deletes exactly the same set. Good output from the first line is a short list of backups you recognize. If it lists a plugin’s or theme’s own zip files, narrow the search to the backup folder instead of ~.
Better still, delete old backups from inside the backup plugin’s own screen, which also cleans up its records, and set it to keep only one or two local copies with the rest sent to remote storage.
Use your cache plugin’s “purge all” or “clear cache” button first, then check the folder size again. If the plugin cannot run because the site is down, empty the folder’s contents (not the folder itself):
find wp-content/cache -mindepth 1 -maxdepth 1 -print
find wp-content/cache -mindepth 1 -delete
Run these over SSH from the site folder. -mindepth 1 keeps the cache folder itself so the plugin can write to it again. The first line lists the top-level items so you can confirm this really is a cache folder; the second removes everything inside. The cache rebuilds as visitors load pages.
ls -la wp-content/upgrade wp-content/upgrade-temp-backup
Run this over SSH from the site folder. These folders should be empty or missing when no update is running. If no update is in progress, anything inside is safe to remove: delete the contents, not the folders, with the same preview and delete pattern as the cache step.
Empty Spam and Trash in webmail, delete or archive old mail, and set per-mailbox quotas in the panel so one mailbox cannot fill the account. Do not delete files under ~/mail by hand unless you know the mailbox format, because you can corrupt the mailbox index.
mysql -e "SHOW BINARY LOGS;"
mysql -e "PURGE BINARY LOGS BEFORE 'YYYY-MM-DD HH:MM:SS';"
Run these over SSH on a server you manage, as a database user with the right privilege. YYYY-MM-DD HH:MM:SS is a cutoff date and time; logs older than it are removed. The first command lists log files and sizes, so you can choose a cutoff. Never delete binlog files with rm: the server keeps an index of them and will complain or fail to start. If you use replication, make sure every replica has read past the cutoff. Then set an expiry so they do not pile up again: binlog_expire_logs_seconds on MySQL 8, and expire_logs_days or binlog_expire_logs_seconds on MariaDB depending on version.
Remove node_modules from production themes and plugins after confirming the theme’s built files (usually in a dist or build folder) are what the site loads. Your build process should deploy built assets, not dependencies.
Run df -h and df -i again, or refresh the panel. Then test the things that failed: upload an image, run a pending update, submit a form, send a test email to an address on your domain. Check the database is healthy:
wp db check
Run this over SSH from the site folder. It runs the database’s own table check on every WordPress table. Good output lists each table followed by OK and ends with “Success: Database checked.” Any table marked as crashed or corrupt should be repaired with wp db repair after you have a fresh database export.
If df says the disk is full but du cannot account for the space, a process may be holding a deleted file open. The space is not freed until the process closes it. This often happens when someone runs rm on a log that a service is still writing.
sudo lsof +L1 2>/dev/null | sort -k7 -rn | head -20
Run this over SSH on a server you manage. lsof +L1 lists open files with a link count below one, meaning they have been deleted but are still open. The sort -k7 -rn sorts by the size column, largest first. Output names the process (for example php-fpm, mysqld or nginx) and the deleted path. Restarting or reloading that service releases the space. This is also why Step 4 empties active logs with : > instead of deleting them.
On servers with systemd, the journal can grow large. Check it with journalctl --disk-usage and trim it with sudo journalctl --vacuum-size=SIZE, where SIZE is the amount to keep, such as 500M. Make sure logrotate covers your web server, PHP-FPM and any custom logs.
If you look after several sites, a scheduled task that emails you df -h, df -i and the top ten folders from the du command each week will catch growth long before it becomes an outage. Watch especially for steady week-on-week growth in backup and log folders, and for sudden jumps in file counts under wp-content/cache or wp-content/uploads, which can also be a sign of a compromise writing spam files.
Open a ticket if the panel and du disagree about your usage, if you cannot find where the space went, if the database will not start after you freed space, or if you need a temporary quota increase to get the site back while you clean up. Send:
df -h and df -i, with the UTC time you checked;du and inode commands;If the site is down right now, start at the Emergency Room, and use our diagnostic tools to check it is responding again once you have freed space.
Did this fix it?
Still stuck? Start again from what you can see in the Emergency Room.
Still stuck?
Print a free one-page checklist to keep, or open the Emergency Room if the symptom does not match.
Get the free checklistsKeep reading
Search the field manual
Or jump straight to a symptom
Press / to search from any page, Esc to close