Mumbai & Delhi datacenters 99.9% uptime SLA
All systems operational New Build a website with AI
Navigation
AI Website Builder New Pricing About Documentation Support Center Get Started Login
ALL SYSTEMS OPERATIONAL
VPS

VPS Out of Inodes: Fix 'No Space Left on Device' Errors

Getwebup 6 min read

You SSH in, run df -h, and the disk shows 40% free — but every write still fails with "No space left on device." That's not a disk space problem. It's an inode problem, and it needs a completely different fix than freeing up gigabytes.

Symptom: plenty of space, but nothing will write

This one confuses people because every instinct says "check disk usage first," and disk usage looks fine. What you'll actually see:

  • PHP, MySQL, or your app logs errors like No space left on device or ENOSPC
  • You can't create new files, but editing existing ones works fine
  • apt install or yum update fails partway through extracting packages
  • WordPress media uploads or session files fail with vague "unable to write" errors
  • df -h shows 30-60% used — nowhere close to full

That last point is the tell. A genuinely full disk and an exhausted inode table produce the exact same symptom — a failed write — but df -h only tracks one of them.

Cause: an inode table is a fixed count, not a size limit

Every file and directory on a Linux filesystem, no matter how small, consumes exactly one inode — a metadata slot that stores ownership, permissions, and a pointer to the file's data blocks. On ext4 (the default on almost every VPS image), the total number of inodes is decided once, at mkfs time, based on an assumed average file size. It does not grow automatically as you use the disk.

So a 20GB volume formatted with a lot of small files in mind might have 1.3 million inodes. If something on that server starts creating millions of tiny files — session caches, mail queue files, a runaway logger, a poorly-configured cron job — you can burn through every inode while 60% of the actual bytes sit empty. Once the inode table is full, the kernel refuses new files even though there's plenty of physical space left to store them.

Common culprits we see on Getwebup VPS instances:

CulpritTypical locationWhy it explodes
PHP session files/var/lib/php/sessionsDefault file-based sessions never expire cleanly under load
Mail queue/var/spool/exim or /var/spool/postfixA compromised script or contact form spamming outbound mail
npm / node_modules~/app/node_modulesNested dependency trees can hit tens of thousands of tiny files per project
Application cachewp-content/cache, framework tmp dirsObject cache or page cache writing one file per request, never cleared
Docker layers/var/lib/docker/overlay2Each image layer and container adds thousands of small files
Log rotation not running/var/logRotated logs pile up as separate files instead of being compressed and pruned

Fix: confirm it, find it, clear it

Step 1 — confirm it's actually inodes

Run both checks side by side — this is the only way to tell the two problems apart:

df -h    # disk space by partition
df -i    # inode usage by partition

If df -h shows comfortable free space but df -i shows IUse% at 95-100% on the same partition, you've confirmed it.

Step 2 — find where the inodes went

Don't guess — walk the tree from the root down and count files per directory, drilling into whichever one is heaviest:

for d in /*/; do
  echo -n "$d: "
  find "$d" -xdev | wc -l
done | sort -t: -k2 -n -r

Once you've identified the heavy top-level directory (usually /var or /home), repeat one level deeper inside it until you land on the actual folder responsible. It's almost always one of the culprits in the table above, not a spread across the whole disk.

Step 3 — clear it immediately

Once you've located the offender, clear it with the tool built for that job rather than a blind rm -rf:

# Stale PHP sessions older than 24 hours
find /var/lib/php/sessions -type f -mtime +1 -delete

# Exim queue — count first, then flush if it's a compromise
exim -bpc
exim -qff

# Docker dangling layers and stopped containers
docker system prune -a --volumes

# npm cache and unused node_modules in old deploys
npm cache clean --force
find / -xdev -name "node_modules" -type d -prune -mtime +30 -exec rm -rf {} \;

Check df -i again after each step — you want to see IUse% actually drop before moving to the next directory.

Step 4 — if it keeps happening, the volume is genuinely undersized

This is the part people get wrong: resizing the disk with resize2fs after a VPS storage upgrade grows the available space, but it does not add new inodes to an existing ext4 filesystem — the inode table was allocated at format time and stays fixed. If your workload is inherently file-count-heavy (large session volume, a queue-heavy app, lots of small cached assets), you have two real options:

  • Reformat with a higher inode ratio. When you rebuild the filesystem, pass mkfs.ext4 -N <inode-count> or a smaller -i bytes-per-inode value to allocate more inodes up front. This requires backing up, reformatting, and restoring — plan it as a maintenance window.
  • Move to XFS for that volume. XFS allocates inodes dynamically as needed instead of fixing the count at format time, which sidesteps this entire class of problem for workloads that generate huge numbers of small files.

Either way, don't just attach more disk space and assume it's fixed — check df -i on the new volume before you call it done.

Prevention: stop landing back here

  • Move PHP sessions to Redis or Memcached instead of the filesystem if your app creates a high volume of them — this removes the biggest recurring offender entirely.
  • Add df -i to your monitoring alongside df -h — a disk-space alert alone will miss this failure mode completely.
  • Confirm logrotate actually runs (logrotate -d /etc/logrotate.conf for a dry run) rather than assuming the cron job is healthy.
  • Set a retention policy on Docker images and cache directories instead of letting them grow unbounded between deploys.
  • If you provision new volumes, decide the expected average file size upfront and format accordingly — it's much cheaper to get the inode ratio right at mkfs time than to migrate later.

A full inode table is completely recoverable once you know to check for it — the trap is spending an hour staring at df -h and a clean disk, wondering why nothing will write.

Frequently asked questions

How is inode exhaustion different from a full disk?

A full disk means you've used up the actual storage bytes on the volume. Inode exhaustion means you've used up the fixed number of file-metadata slots the filesystem allocated at format time — you can hit this with plenty of free space left if you have a huge number of very small files.

Will resizing my VPS disk fix inode exhaustion?

No. Growing an existing ext4 filesystem with resize2fs adds space but doesn't add inodes to the original filesystem's inode table. You need to reformat with a higher inode count (or a different filesystem like XFS) to actually add more inodes.

How do I check inode usage on my VPS?

Run df -i alongside df -h. df -h shows byte usage; df -i shows the percentage of inodes used per mounted partition. If df -i is near 100% while df -h looks fine, you're dealing with inode exhaustion, not disk space.

What usually causes inode exhaustion on a hosting VPS?

The most common causes are file-based PHP session storage, a runaway or compromised mail queue, unpruned Docker image layers, deep node_modules trees, and application caches that write one file per request without ever clearing old ones.

Can I switch an existing ext4 volume to XFS without losing data?

Not in place. You'd need to back up the data, reformat the volume as XFS (or a new ext4 filesystem with a higher inode allocation), and restore from backup. Treat it as a planned maintenance window, not a live change.

#vps #inode-exhaustion #ext4 #disk-space #linux #filesystem

Keep reading

Chat with Support