Linux: "No space left on device" but df shows free space (inodes)
Hi, it's BlueByte. Your disk has gigabytes free, df -h agrees, and yet every write fails with No space left on device. Nothing is corrupt — the filesystem has simply run out of inodes, and df -h never shows that. The symptom is ENOSPC / No space left on device on a touch, a cp, a log write, or an application error, while df -h reports plenty of room. We'll walk through why free space and "no space left" can both be true at once, how to confirm it's inodes, find what's eating them, fix it, and keep it from coming back.
Why a disk with free space still says "No space left on device"
Every file on an ext4 filesystem needs two things: data blocks to hold its contents, and one inode to hold its metadata — owner, permissions, timestamps, and where the blocks live. df -h measures blocks. But the number of inodes is decided when the filesystem is created and, per the mke2fs manual, "it is not possible to change this ratio on a file system after it is created." A directory full of tiny files burns one inode each while barely touching the blocks. When the inodes run out, the kernel returns ENOSPC — the same error a full disk gives — even though df -h still shows free gigabytes. Two things also mimic this: writing to a different mount than the one you checked, and a deleted-but-still-open file — though that last one shows up as full in df -h, so it's the opposite tell.
First, ask df for inodes, not blocks
Don't trust df -h alone here — ask for inode usage with -i, which the coreutils manual defines as "list inode usage information instead of block usage":
df -h /
df -i /Filesystem Size Used Avail Use% Mounted on
/dev/sda1 193G 135G 58G 71% /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 26083328 3502488 22580840 14% /That is a healthy filesystem — 14% of inodes used, room in both columns. On an exhausted one the IUse% column reads 100% while df -h's Use% still shows space free. If IUse% is 100, you've found it. If it isn't, make sure you're even looking at the right filesystem: run df -i against the exact path that fails (df -i /var/lib/app), because the write may land on a separate mount.
Find the directory drowning in tiny files
Inodes are gone because something created a mountain of small files. Find the directory holding them by counting files per parent:
sudo find / -xdev -type f -printf '%h\n' | sort | uniq -c | sort -rn | head4211872 /var/spool/postfix/maildrop
38214 /var/log/journal/9f0a...
9021 /home/app/.cache-xdev keeps find on one filesystem so it doesn't wander into /proc or another mount. The top line — over four million files in one mail spool — is the culprit. That single directory drained the inode table.
Fix it by clearing the files that ate the inodes
Once you know the directory, delete what's safe to delete. A directory with millions of entries is too large for rm * — the shell can't expand that many arguments and errors with Argument list too long — so delete in place:
sudo find /var/spool/postfix/maildrop -type f -deleteExpected result: df -i drops right away as the inodes are freed. If the files are legitimate and you genuinely need more inodes, there is no live resize — you recreate the filesystem with a denser inode ratio using mke2fs -i <bytes-per-inode> or a fixed count with -N, then restore from backup. This applies to ext2/3/4; XFS and Btrfs allocate inodes dynamically and don't hit a fixed cap the same way, so you'd rarely see this on them.
A real case: a runaway session directory
A PHP app starts throwing failed to open stream: No space left on device, but df -h / shows 60 GB free. You run df -i / and IUse% is 100%. The file-count scan points at /var/lib/php/sessions holding six million session files that were never expired. You confirm they're stale with find /var/lib/php/sessions -type f -mtime +2 | head, then clear them with sudo find /var/lib/php/sessions -type f -mtime +2 -delete. df -i falls to 20%, and the app writes again — the blocks were never the problem.
Verify the inodes came back and writes succeed
Check the same column you started with, then prove a write works:
df -i /
touch /var/lib/app/probe && echo "write ok" && rm /var/lib/app/probe/dev/sda1 26083328 5231044 20852284 21% /
write okAn IUse% well under 100 and a successful touch mean the filesystem can create files again. If touch still fails, you cleared the wrong directory — re-run the file-count scan.
Keep the inode table from filling up
Expire small files before they pile up: give mail spools, session stores, and cache directories a retention policy with systemd-tmpfiles or a cron find -mtime +N -delete. Monitor df -i, not just df -h — an alert on IUse% catches this days before it stops writes. And when you build a filesystem you know will hold many small files, size the inode ratio for it at mke2fs time, since you can't change it afterward.
How this differs from a full disk and a deleted-open file
If df -h shows Use% 100%, the blocks are full — that's an ordinary disk-full, and on a container host it's usually image and log layers you clear with docker system prune, not this. If both df -h and df -i look fine yet space seems to have vanished, a process is holding a deleted file open, so its blocks aren't reclaimed until it closes — find it with sudo lsof +L1. Inode exhaustion is the only one of the three where df -h shows free space while df -i shows full.
Related questions
df -h shows plenty of free space, so why does every write fail?
Because df -h only counts data blocks, not inodes. Each file needs one inode, and if the fixed inode pool is exhausted the kernel returns the same No space left on device (ENOSPC) despite free blocks. Run df -i and read the IUse% column.
Can I add more inodes without reformatting?
No. On ext2/3/4 the inode count is set at mkfs time and can't be resized live. You recreate the filesystem with mke2fs -i (bytes-per-inode) or -N (a fixed count) and restore from backup. XFS and Btrfs allocate inodes dynamically and don't have this fixed cap.
rm * fails with 'Argument list too long' in the full directory.
The directory has too many entries for the shell to expand into arguments. Delete in place with find <dir> -type f -delete, which never builds one huge argument list.
df -i also looks fine, but I still get No space left on device.
Two possibilities: the write goes to a different mount than the one you checked (run df -i on the exact failing path), or a process is holding a deleted file open so its blocks aren't freed — find it with sudo lsof +L1 and restart that process.
Does this happen on XFS?
Rarely. XFS allocates inodes dynamically as files are created, so it doesn't hit a fixed inode ceiling the way ext4 can. A genuinely full disk (blocks, not inodes) still returns ENOSPC on any filesystem.
References
Haneul Seo
Infrastructure engineer · 10+ years running Linux fleets
More in this category
systemd: Start request repeated too quickly
systemd refuses to start a unit that was started more than StartLimitBurst times (default 5) within StartLimitIntervalSec (default 10s), and Restart= counts against that limit. With the 100 ms default RestartSec a crashing service burns all five attempts in under a second. Find the real crash in the journal, fix it, run reset-failed, and give restarts room with RestartSec.
SSH: Received disconnect ... Too many authentication failures
Your agent is offering more keys than the server will let you try. Every public key sshd looks at burns one of the MaxAuthTries attempts — six by default, often three on a hardened host — so the right key never gets its turn and the server hangs up before you type anything. IdentitiesOnly=yes with an explicit IdentityFile pins the connection to one key and the attempt count drops to one.
Active Directory: replication fails with error 1722, The RPC server is unavailable
RPC reports 1722 (0x6ba, RPC_S_SERVER_UNAVAILABLE) when a lower layer fails to connect, so the real fault is almost never RPC itself — it is DNS, a blocked port, or a host-side setting on one of the two domain controllers. repadmin tells you which partner is failing, dcdiag /test:dns rules out name resolution, and Test-NetConnection plus the dynamic port range settle the firewall question. The most common miss is a rule that allows TCP 135 but not 49152–65535.
Windows Server RDS: The remote session was disconnected because there are no Remote Desktop License Servers available to provide a license
The 120-day RD Licensing grace period ended and the session host has no usable license server, so it refuses sessions. GetGracePeriodDays returning DaysLeft 0 and an empty SpecifiedLSList confirm it in seconds. The fix is a real, activated license server with CALs that are new enough for the host — a 2019 CAL cannot serve a 2022 session host — configured through the deployment or the Licensing policies, plus RPC ports open between the two.
Windows 11: "Your organization's security policies block unauthenticated guest access" when opening a NAS share (0x80070035)
The SMB client on Windows 10 Enterprise/Education/Pro for Workstations, Windows 11 Pro and Windows Server 2019+ refuses guest logons by default, and Windows 11 24H2 Enterprise/Pro/Education also requires SMB signing, which guest sessions can't do. A NAS share that only offers guest access therefore fails with the 'block unauthenticated guest access' dialog, Error code 0x80070035, or System error 3227320323, and Event ID 31017 'Rejected an insecure guest logon' lands in the SmbClient/Security log. The fix Microsoft recommends is a real account on the NAS and signing support in its firmware; Set-SmbClientConfiguration -EnableInsecureGuestLogons $true (plus -RequireSecuritySignature $false on 24H2) is the escape hatch, and it costs you signing and encryption on that client.
Ubuntu/Debian: E: Could not get lock /var/lib/dpkg/lock-frontend — who holds it and how to wait for it
Another package manager, usually Ubuntu's unattended-upgrades fired by a persistent systemd timer at boot, holds the dpkg frontend lock while your apt-get runs. apt-get gives up at once while apt waits because Ubuntu ships binary::apt::DPkg::Lock::Timeout "120" for the apt binary only. Read the PID from the message, let the run finish or pass -o DPkg::Lock::Timeout=<seconds> to apt-get, run dpkg --configure -a only after a genuinely interrupted run, and never delete the lock file: it is an fcntl lock the kernel releases when the holder exits.