Here's a confusing one: your VPS won't let you write a file, PHP throws a fatal error trying to create a session, or apt refuses to install anything — and the error is No space left on device. So you run df -h to see how bad it is, and it tells you you've got 40GB free. That's not a typo. Your disk isn't full. Your inodes are.

What's Actually Happening

Every file and directory on an ext4 or XFS filesystem needs an inode — a small metadata record that tracks ownership, permissions, and where the file's data blocks live. When the filesystem was formatted, a fixed number of inodes was set aside for it, sized roughly for "average" file sizes. If your server ends up with millions of tiny files — think PHP session files, mail queue items, cache fragments, or leftover build artifacts — you can burn through every available inode long before you run out of actual disk space in bytes.

Once the inode table is full, the kernel refuses to create anything new: no new files, no new directories, not even empty ones. Existing files can usually still be read and even appended to, which is part of why this is so disorienting — the site "mostly" works, until it doesn't.

Confirm It's Inodes, Not Space

Don't guess — check both. These two commands look almost identical but measure completely different things:

df -h    # disk space in bytes
df -i    # inode usage

If df -h shows plenty of headroom but df -i shows IUse% at or near 100% for the mount point that's failing, you've found your culprit. Something like this:

Filesystem      Inodes   IUsed   IFree IUse% Mounted on
/dev/vda1      6553600 6553600      0  100% /

That's a fully saturated inode table on a root partition with plenty of free bytes sitting unused. This is the smoking gun.

Find What's Eating Your Inodes

You need to find which directory holds the most files, not which one takes the most space — a very different question. A quick top-level scan:

for d in /*; do
  echo -n "$d: "
  find "$d" -xdev -type f 2>/dev/null | wc -l
done | sort -t: -k2 -rn

Run it again scoped to whichever directory shows the highest count, narrowing down one level at a time, until you land on the actual offender. ncdu -x can also help if you toggle it to count files rather than sizes.

The usual suspects, in rough order of how often we see them:

LocationWhat piles up
/var/lib/php/sessions/Stale PHP session files when garbage collection isn't running (common on custom nginx-primary-script-unknown-file-not-found-on-a-vps" class="auto-link">PHP-FPM pools)
/var/spool/exim4 or /var/spool/mailA stuck mail queue retrying forever, one file per message + header
*/node_modules/.cacheBuild tool caches (webpack, babel) with hundreds of thousands of tiny cache chunks
/var/lib/docker/overlay2/Dangling images and unpruned build layers
cpanel" class="auto-link">WordPress cache plugin directoriesPage-cache plugins that never expire old fragment files
/var/log/An app logging per-request instead of appending, one file per hit

Cleaning It Up

Once you've identified the directory, deal with it directly rather than deleting blindly:

  • PHP sessions: clear old ones and fix the root cause — find /var/lib/php/sessions -type f -mmin +1440 -delete, then check that session.gc_probability and session.gc_maxlifetime in php.ini are actually enabled (some distro packages disable the probabilistic cleanup by default and rely on a cron that may not exist on your box).
  • Mail queue: check the queue size first (exim -bpc or mailq | grep -c '^[A-Z0-9]' for Postfix). If it's thousands of frozen messages, that's usually a relay or auth problem worth fixing before you just wipe the queue.
  • Docker: docker system prune -a --volumes reclaims both inodes and disk space from stopped containers, dangling images, and orphaned layers.
  • Logs: confirm logrotate is actually running (logrotate -d /etc/logrotate.conf for a dry run) and that it's configured for any app logging outside the standard /var/log rotation rules.
  • node_modules / build caches: safe to delete wholesale — rm -rf node_modules/.cache and rebuild.
Resist the urge to rm -rf a directory you don't fully understand just because it has a lot of files. A bloated mail queue or session directory is often a symptom of something upstream (a broken cron, a misconfigured relay) that will just refill the same directory in a few hours if you don't fix the cause too.

Prevention

Inode exhaustion is one of those problems that's invisible until it isn't, so build a check for it into your normal monitoring instead of only your normal disk-space check:

  • Add df -i to whatever health check or cron alert you already run for disk space — most teams monitor bytes and completely forget inodes.
  • If you're on our SkyServer VPS plans, Netdata (covered in our real-time monitoring guide) tracks inode usage per mount point out of the box — worth enabling an alert threshold at 85%.
  • Move session storage off disk entirely if traffic is high — Redis-backed PHP sessions sidestep the whole class of problem.
  • Set a weekly cron to prune Docker layers and clear build caches before they accumulate for months.
  • When provisioning a new VPS with an unusual workload (millions of small files by design, like a mail server or a cache layer), consider formatting a dedicated data partition with a higher inode ratio at creation time — you can't easily change it after the fact without reformatting.

Frequently Asked Questions

Can I increase the number of inodes on an existing ext4 partition?

Not without reformatting. The inode count is fixed at mkfs time based on the bytes-per-inode ratio. If you're chronically inode-starved on a data partition, the real fix is either freeing up files, moving the workload to a filesystem you reformat with a lower bytes-per-inode ratio, or migrating that data to a dedicated volume sized for the file count you actually have.

Does XFS have the same inode limit problem?

XFS allocates inodes dynamically rather than reserving a fixed count upfront, so in practice it's much harder to exhaust — though it's still bounded by available disk space for inode metadata. If you're regularly hitting inode ceilings on ext4, XFS is worth considering for that particular volume.

Why does df -h show free space if the disk is technically "full"?

Because inodes and data blocks are separate resource pools. df -h reports the data-block pool (actual file content); df -i reports the inode pool (file/directory metadata slots). You can exhaust either one independently of the other — a directory full of millions of empty files can exhaust inodes while barely touching disk space.

I deleted files but df -i still shows 100% used. Why?

If a process still has the file open (a common case: a log file that was deleted but a running daemon still holds the file descriptor), the inode isn't actually released until that handle closes. Restart or reload the offending service, or use lsof | grep deleted to find what's still holding it open.

Will a full inode table crash my server?

Not directly, but it will break anything trying to create new files — including logging, cron jobs writing lock files, PHP sessions, and package managers. Existing running processes generally keep working until they need to write something new, which is why the symptoms can look scattered and unrelated at first.