Diagnose and Fix "No Space Left" When Disk Is Empty (Inodes)
"No space left" with gigabytes free? You've hit inode exhaustion. Learn how to confirm it, find the culprit directories, clean up millions of files, and prevent recurrence.
Before you start
- ▸Root or sudo access on the affected system
- ▸Basic familiarity with the Linux command line
- ▸Enough free inodes to run a handful of commands (if completely saturated, use an existing shell session)
You get "No space left on device" errors, but df -h shows gigabytes free. The culprit is almost always inode exhaustion. Every filesystem has a fixed pool of inodes — one per file, directory, and symlink — and when that pool runs dry, no new files can be created regardless of how much block space remains. This guide walks through diagnosing the problem, finding the offending directories, and applying lasting fixes.
What Are Inodes?
An inode stores the metadata for a file: permissions, owner, timestamps, and pointers to data blocks. The inode count for most filesystems (ext3, ext4, xfs with certain configurations) is set at format time and cannot be increased without reformatting. A filesystem formatted with default settings gets roughly one inode per 16 KB of capacity. On a 20 GB partition that sounds like plenty — but if a mail spool, a package cache, or a build system generates millions of tiny files, you can exhaust inodes while barely touching disk capacity.
Step 1: Confirm Inode Exhaustion
Check inode usage across all mounted filesystems with the -i flag:
df -i
Look for any filesystem where IUse% is at or near 100%. Output will vary, but a saturated filesystem looks like:
# Example — your numbers will differ
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 1310720 1310720 0 100% /
tmpfs 499712 621 499091 1% /run
Confirm from the application side as well:
touch /tmp/testfile && echo "OK" || echo "FAILED"
If you see "cannot touch: No space left on device" while df -h shows free space, inodes are the issue.
Step 2: Find Which Directory Holds the Most Files
The kernel only tells you inodes are gone — you have to find the pile yourself. The most reliable method is find combined with sorting. Run this on the affected mount point (replace / with the specific mount if it is not root):
find / -xdev -printf '%h\n' | sort | uniq -c | sort -rn | head -20
-xdev prevents crossing into other mounted filesystems. %h prints the parent directory of each file. The result ranks directories by file count. On a busy system this may take a minute or two.
A faster alternative uses du in inode-counting mode (available on Linux du from GNU coreutils 8.25+):
du --inodes -xS / | sort -rn | head -20
-S excludes subdirectory counts so each line reflects only files directly inside that directory, making the hot spots obvious.
Step 3: Identify the Common Culprits
Once you have a ranked list, cross-reference these frequent offenders:
- Mail spools —
/var/mailor/var/spool/mail; each message is a separate file in Maildir format. - Session files — PHP applications often write one file per session to
/var/lib/php/sessions. - Apt / dpkg cache —
/var/cache/apt/archivesand/var/lib/dpkg/infoaccumulate over years. - Systemd journal —
/var/log/journalcan balloon if rate-limiting is not configured. - Node.js / npm — a single
node_modulestree can contain 200 000+ files. - Temporary files — rogue processes writing to
/tmpwithout cleanup. - Container layers — Docker/Podman overlay storage in
/var/lib/dockeror/var/lib/containers. - Build artifacts — Rust and Go build caches,
.ccache, Maven.m2repositories.
Step 4: Clean Up the Inode Hog
Package Caches
Debian/Ubuntu:
sudo apt clean
sudo apt autoclean
Fedora/RHEL family:
sudo dnf clean all
Arch:
sudo pacman -Sc
PHP Session Files
find /var/lib/php/sessions -type f -mtime +7 -delete
Systemd Journal
sudo journalctl --vacuum-time=14d
Docker / Podman Dangling Layers
# Docker
sudo docker system prune -f
# Podman
podman system prune -f
Mass-Delete Millions of Files Safely
Regular rm -rf can crash or hang when argument lists overflow. Use find ... -delete instead, which avoids shell argument limits entirely:
find /path/to/directory -type f -mtime +30 -delete
If you need to wipe an entire directory tree as fast as possible, rsync with an empty source is faster than rm for very large file counts:
mkdir /tmp/empty
rsync -a --delete /tmp/empty/ /path/to/large-directory/
Step 5: Add Automated Prevention
Cleaning up once is not enough. Put a systemd timer in place for recurring cleanup rather than relying on cron (though cron works fine too).
Create the service unit:
sudo tee /etc/systemd/system/session-cleanup.service <<'EOF'
[Unit]
Description=Clean stale PHP session files
[Service]
Type=oneshot
ExecStart=find /var/lib/php/sessions -type f -mtime +7 -delete
EOF
Create the timer unit:
sudo tee /etc/systemd/system/session-cleanup.timer <<'EOF'
[Unit]
Description=Run PHP session cleanup daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now session-cleanup.timer
Step 6: Long-Term — Restructure or Reformat
If the workload genuinely requires millions of files (a mail server, an object store), the correct long-term solution is a filesystem designed for high inode density or one without a fixed inode limit.
- XFS — allocates inodes dynamically; it will not run out of inodes unless it runs out of disk space. Ideal for mail spools and object storage. Reformat requires a backup and restore.
- ext4 with a smaller inode ratio — format with
mke2fs -t ext4 -i 4096 /dev/sdXNto get one inode per 4 KB instead of the default 16 KB, quadrupling inode density. Only possible at format time. - tmpfs for sessions — mount a dedicated tmpfs for volatile small-file directories so they never consume persistent inodes:
echo "tmpfs /var/lib/php/sessions tmpfs defaults,size=256m,mode=1777 0 0" | sudo tee -a /etc/fstab sudo mount -a - Object storage patterns — if an application writes millions of tiny files, consider migrating to SQLite, a key-value store (Redis, LMDB), or an object store (MinIO) so the filesystem sees fewer entries.
Verification
After cleanup, confirm inodes are freed:
df -i /
Then confirm file creation works again:
touch /tmp/inode-test && echo "Inodes free" && rm /tmp/inode-test
Troubleshooting
- df -i shows low usage but errors persist — the problem may be on a specific bind-mount or overlay filesystem. Run
df -ifor all mounts and check/proc/mounts. Container runtimes can exhaust inodes on their own overlay layer independently of the host root. - find runs for hours — use
-xdevto avoid scanning network mounts (NFS, CIFS). Also try targeting/varfirst since that is where most runtime garbage lives. - Cannot delete files as root — check for immutable flags:
lsattr /path/to/file. If you see----i----, remove the flag withchattr -i /path/to/file. - Inode count does not drop after deletion — open file descriptors from running processes hold inodes allocated. Check
lsof +L1for deleted-but-open files. The count normalises once those processes close or restart.
Frequently asked questions
- Can I increase the inode count on an existing ext4 filesystem without reformatting?
- No. The inode table on ext4 is fixed at mkfs time and cannot be expanded online or offline. Your options are to free up existing inodes, reformat with a higher inode density (which requires a full backup and restore), or migrate to XFS, which allocates inodes dynamically.
- Why does XFS not have this problem?
- XFS allocates inode structures dynamically from free space as files are created, so it shares the same pool as data blocks. You cannot exhaust inodes without also exhausting disk capacity, which makes the disk-full and inode-full conditions converge into one problem.
- My container keeps hitting inode limits even though the host filesystem has plenty free. Why?
- Container runtimes like Docker and Podman use overlay filesystems that have their own inode accounting. The overlay mount point — typically under /var/lib/docker or /var/lib/containers — sits on the host filesystem, but dangling image layers and stopped container writable layers each consume many inodes. Run docker system prune or podman system prune to reclaim them.
- After I deleted millions of files, df -i still shows the same inode count used. Why?
- A process still has those files open. The kernel keeps the inode allocated until the last file descriptor is closed, even after the directory entry is removed. Run lsof +L1 to list deleted-but-open files, then restart or signal the owning process.
- Is there a monitoring alert I can set so this never surprises me again?
- Yes. Most monitoring stacks (Prometheus node_exporter, Zabbix, Nagios/Icinga) expose inode metrics. In node_exporter the metric is node_filesystem_files_free. Set a warning alert at 20% free and a critical alert at 5% free so you have time to investigate before the filesystem saturates.
Related guides
Back Up Linux with Borg or restic
Set up encrypted, deduplicated backups with BorgBackup or restic: local and remote repos, retention pruning, restoring files, and systemd timer scheduling.
How to Check Disk Health with SMART
Learn to use smartctl to read SMART attributes, run drive self-tests, and identify early warning signs of HDD and SSD failure before data loss occurs.
Debug systemd Units that Won't Start
Learn a repeatable workflow to debug systemd services that won't start: status output, journalctl, systemd-analyze verify, and safe override.conf patches.
Linux Server Disaster Recovery Checklist
A practical Linux server disaster recovery checklist: what to back up, RTO/RPO planning, immutable off-site copies, automated restore drills, and verification.