When WordPress Won’t Update: The Case of the Missing Inodes

Yesterday afternoon I got a call from a client with what seemed like a straightforward problem – WordPress wouldn’t update, and their users couldn’t upload files. The error message was fairly cryptic: “Could not copy file.: wordpress/wp-admin/images/mask.png”. Not particularly helpful.

They mentioned they could update WordPress manually via SFTP without any issues, but the automatic updates kept failing. That’s usually a permissions problem, so I started there. But this turned into quite the detective story.

The Obvious Suspects

When WordPress updates fail, there’s a pretty standard checklist to work through. File permissions are usually the culprit – the web server user needs to own the files it’s trying to update. So I checked that first. All correct. The PHP user owned everything it should.

Disk space? Over 1TB free according to the hosting panel. PHP memory limits? A generous 256MB. Everything looked fine.

But when I tried to simulate the update process with a test script, I hit something unexpected: “Disk quota exceeded”. That didn’t make sense with all that free space.

The Plot Twist

Turns out there are two types of disk limits on shared hosting. There’s disk space (how much data you can store), and there’s inodes (how many files you can have). Think of it like a car park – you might have space for more cars, but if you’ve run out of parking permits, you still can’t park.

This hosting account had a limit of 262,144 files. They were using 259,564. That’s 99% of their inode quota with only 2,580 files left. WordPress updates need to extract thousands of temporary files. No wonder it was failing.

Finding the Culprit

Since the hosting didn’t provide SSH access, I wrote a Python script to scan the entire hosting package via SFTP. What it found was shocking:

wp-content/uploads: 172,304 files
  2019: 186 files
  2020: 372 files
  2021: 11,872 files
  2022: 7,103 files
  2023: 20,915 files
  2024: 71,980 files
  2025: 59,868 files (through to July!)

Over 172,000 files just in the uploads folder! That’s 66% of their entire inode allowance in one directory. Something had clearly gone wrong in 2024.

The WordPress Thumbnail Problem

After some investigation, I found the issue. WordPress was configured with 11 different image sizes:

  • thumbnail, medium, medium_large, large (the standard ones)
  • 1536×1536, 2048×2048 (WordPress 5.3+ responsive images)
  • entry, entry-cropped, entry-fullwidth, entry-cropped-fullwidth (theme-specific)
  • post-thumbnail (another theme size)

Every single uploaded image was creating 12 files – the original plus 11 resized versions. Upload 100 images? That’s 1,200 files eating into your inode quota.

The maths was pretty stark. In 2024 they’d uploaded around 6,000 images, which turned into 72,000 files. At that rate, they’d hit the inode limit within months.

The Fix

First, I needed to free up inodes immediately so the site could function. I wrote a PHP script that would safely delete unnecessary thumbnails from older uploads (2019-2023), but only where the original image still existed. No point keeping thumbnails if we’ve lost the original.

The script was pretty straightforward – scan each uploads directory, identify thumbnails by their filename pattern (image-150×150.jpg), check if the original exists (image.jpg), and only delete if it does. This freed up about 50,000 inodes immediately.

For the long term, I added some code to their theme’s functions.php to prevent the problem recurring:

// Remove unnecessary image sizes
function disable_unused_image_sizes() {
    remove_image_size('1536x1536');
    remove_image_size('2048x2048');
    // Remove theme-specific sizes if not needed
    remove_image_size('entry');
    remove_image_size('entry-cropped');
    remove_image_size('entry-fullwidth');
    remove_image_size('entry-cropped-fullwidth');
}
add_action('init', 'disable_unused_image_sizes');

We also tackled a secondary issue – the database had hit its 2GB limit due to thousands of post revisions. A quick cleanup of revisions older than 6 months freed up enough space there too.

Lessons Learned

This was a good reminder that “disk quota exceeded” doesn’t always mean what you think it means. On shared hosting, you need to watch both disk space and inode usage. The hosting panel might say you have tons of space free, but if you’re out of inodes, you’re still stuck.

If you’re running into similar issues, here’s a quick way to check your inode usage if you have SSH access:

# Check inode usage
df -i

# Find directories with the most files
find . -type d -exec sh -c 'echo "$(find "$1" -type f | wc -l) $1"' _ {} \; | sort -nr | head -20

Or via PHP if you only have web access:

<?php
$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('.')
);
$count = iterator_count($iterator);
echo "Total files: " . number_format($count);

The real lesson though? WordPress’s automatic thumbnail generation can get out of hand quickly. Every theme and plugin can register new image sizes, and before you know it, you’re creating a dozen versions of every upload. It’s worth auditing your registered image sizes occasionally:

<?php
$sizes = get_intermediate_image_sizes();
echo "Your site creates " . count($sizes) . " versions of each image";
print_r($sizes);

In this case, the client’s site is now running smoothly again. WordPress updates work, users can upload files, and we’ve got monitoring in place to catch this before it becomes critical next time. Sometimes the fix isn’t about getting more resources – it’s about using what you have more efficiently.

Have you run into inode limits on your hosting? I’d be curious to hear what caused it in your case.

Leave a Comment

Your email address will not be published. Required fields are marked *