How to Handle inode Exhaustion - When Disk Is Not Full But Writes Fail
What You Will Solve Here
- Understand why writes fail with
No space left on devicewhiledf -hreports free space - Use
df -ito find which filesystem ran out of inodes - Locate the offending directory and reclaim inodes safely by cause (sessions, mail, Docker, logs)
Fastest path (3 steps)
df -ito find the mount point where IUse% is 100%- From that mount, narrow down with
find ... -xdev | cut ... | sort | uniq -c | sort -rnto find the directory hoarding files - Delete by cause (sessions / mail queue / journal / Docker / logs) and prevent recurrence with cron and logrotate
Assumed environment
- OS: Ubuntu / Debian / RHEL family (ext4 assumed)
- Privilege:
sudoavailable - XFS / Btrfs / ZFS allocate inodes dynamically, so this symptom rarely occurs there
What Is inode Exhaustion and Why Does It Happen?
inode exhaustion is the state where the filesystem's metadata table is full even though data blocks are still free, so creating any new file fails.
A filesystem has two ceilings:
- Data blocks: the bytes of file contents. Shown by
df -h. - Inodes: per-file metadata (permissions, owner, size, block pointers). Shown by
df -i. One file consumes one inode.
ext4 uses static inode allocation: the inode count is fixed at mkfs time (default ratio is roughly one inode per 16 KiB). A workload that creates many small files exhausts inodes long before it fills the disk.
Classic symptoms
touch newfilereturnsNo space left on devicedf -hshows usage around 60% — looks "free"df -ishows IUse% = 100%
1. Check inode Usage with df -i
Start with df -i. It reports inode usage per mount point.
$ df -ih
Filesystem Inodes IUsed IFree IUse% Mounted on /dev/sda1 3.8M 3.8M 12K 100% / /dev/sdb1 6.4M 12K 6.4M 1% /data tmpfs 1.0M 5 1.0M 1% /run
The row with IUse% at 100% (or 99%) is the mount point in trouble. In the example above, / is full of inodes.
The -i flag is part of GNU coreutils and works on virtually every Linux distribution. It is not in POSIX but is universally available in practice.
2. Find the Directory Consuming inodes
Drill down from the offending mount point to the directories holding the most files.
2-1. Count files quickly under a path
$ sudo find /var -xdev -printf . | wc -c
Always pass -xdev so find does not cross into other mount points. -printf . emits a single dot per file and wc -c counts bytes — much faster than wc -l on full paths.
2-2. Aggregate file counts by subdirectory
$ sudo find / -xdev -type f -printf '%h\n' 2>/dev/null \
| cut -d/ -f1-4 \
| sort \
| uniq -c \
| sort -rn \
| head -20432105 /var/lib/php 187220 /var/spool/postfix 54221 /var/log/journal 8021 /var/lib/docker ...
cut -d/ -f1-4 groups by depth 3 to reveal the overall shape. When a suspicious path appears, rerun with -f1-6 to drill deeper.
2-3. Count entries in a single directory
$ ls -1U /var/lib/php/sessions | wc -l
ls -1U skips sorting so it stays fast even with millions of entries. wc -l counts lines, which equals the file count.
ls with sorting can run out of memory on directories with millions of entries. Use ls -U or find instead.
3. Fix by Cause
3-1. PHP sessions (/var/lib/php/sessions)
Sessions pile up when session.gc_probability is low or the distro's sessionclean cron stopped running.
# Check count and age first $ sudo find /var/lib/php/sessions -xdev -type f | head $ sudo find /var/lib/php/sessions -xdev -type f -mtime +7 | wc -l # Print before deleting (dry run), then delete $ sudo find /var/lib/php/sessions -xdev -type f -mtime +7 -print $ sudo find /var/lib/php/sessions -xdev -type f -mtime +7 -delete
On Debian/Ubuntu, verify that /etc/cron.d/php runs sessionclean on schedule.
3-2. Postfix mail queue (/var/spool/postfix)
A failing outbound mail or bounce storm can balloon the queue.
$ sudo mailq | tail -1 # queue count $ sudo postqueue -p | tail -1 # same $ sudo postsuper -d ALL deferred # delete only deferred mail $ sudo postsuper -d ALL # delete EVERYTHING (high impact)
postsuper -d ALL deletes all undelivered mail. Confirm no business mail is in the queue before running it.
3-3. systemd journal fragments
$ journalctl --disk-usage $ sudo journalctl --vacuum-time=7d # delete entries older than 7 days $ sudo journalctl --vacuum-size=200M # cap total to 200 MB
Set SystemMaxFiles= in /etc/systemd/journald.conf to prevent recurrence.
3-4. Docker overlay2 layers
$ docker system df $ docker system prune -a --volumes # remove unused images and volumes (review impact first)
See Identifying Docker disk usage for the full breakdown.
3-5. Failed log rotation
$ sudo ls -lh /var/log | head $ sudo logrotate -d /etc/logrotate.conf # dry run $ sudo logrotate -f /etc/logrotate.conf # force run
4. Safety Before Deleting
4-1. Always dry-run first
# Print what would match $ sudo find /tmp -xdev -type f -mtime +30 -print # Delete only after reviewing the list $ sudo find /tmp -xdev -type f -mtime +30 -delete
4-2. Watch for files held open after unlink
A file deleted while a process keeps it open is unlinked but still open — disk and inode are not released until the process closes the descriptor.
$ sudo lsof +L1 | head # files with link count 0 still open
Restart the offending process to release both inode and space.
4-3. Avoid xargs argument overflow
# BAD: command line may overflow or partially execute
$ rm $(find /var/lib/php/sessions -type f)
# GOOD: use -print0 / xargs -0 for safety
$ sudo find /var/lib/php/sessions -xdev -type f -mtime +7 -print0 \
| sudo xargs -0 -r rm --See Safely deleting files with find for the full pattern.
5. Prevent Recurrence: Permanently Increasing inodes
5-1. Inspect the current inode ratio
$ sudo tune2fs -l /dev/sda1 | grep -E 'Inode count|Block count|Inode size'
5-2. ext4: reformat to grow the inode count
# Back up first. -i sets bytes-per-inode; smaller value = more inodes $ sudo mkfs.ext4 -i 8192 /dev/sdb1 # roughly one inode per 8 KB $ sudo mkfs.ext4 -N 10000000 /dev/sdb1 # specify total count
mkfs wipes the filesystem. Snapshot or back up before running it. Always rehearse the procedure in a staging environment before touching production.
5-3. Move to a dynamic-inode filesystem (XFS / Btrfs)
If you can provision a new volume for a workload that creates many small files, XFS or Btrfs eliminates the inode ceiling problem because both allocate inodes on demand.
5-4. Add monitoring
Scrape node_filesystem_files and node_filesystem_files_free (Prometheus node_exporter) and alert when IUse% exceeds 80%. Catching it before 100% gives you time to delete safely instead of in panic mode.
6. What Not to Do
- Removing entire service directories like
rm -rf /var/spool/postfix(Postfix will refuse to start) - Unconditional mass deletes such as
find / -delete - Trying to grow inodes with
tune2fs(not supported on ext4) - Unverified
postsuper -d ALL(risks deleting business mail) - Mashing
Ctrl+Cmid-delete (leaves the FS in a half-cleaned state)