ENOSPC with free blocks, exhausted inodes and a deleted open file
At 03:10 a monitoring alert fires for an Ubuntu 22.04 database host. Applications report 'No space left on device'. df -h /var shows 20G total, 13.9G used, 69% - apparently plenty free. df -i /var shows Inodes 1,310,720, IUsed 1,310,720, IFree 0, IUse 100%. du -sh /var/* sums to 6.1G, with /var/lib/php/sessions at 4.7G and 5.9 million files. A cron job running a PHP session garbage collector was disabled six weeks ago during an upgrade. Separately, lsof +L1 shows a deleted 7.8G file held open by the mysqld process, and the used space reported by df -h does not match du. The host has a 15-minute maintenance window, 2 GB RAM and no spare inodes. The team wants to know what to delete first, what will actually free inodes versus blocks, and how to prevent recurrence.
1. Explain how inodes and blocks can each cause ENOSPC, and which evidence in the setup points to each. Why is df -h alone misleading here?
Read the worked answer
ENOSPC can mean no free data blocks or no free inodes; the kernel returns the same error for both.
- Blocks:
df -hreports space. Here 13.9G of 20G is used, so blocks are not the immediate cause. - Inodes:
df -ireports inode use. Here IUsed equals the total, 1,310,720, and IFree is 0. Every new file needs an inode even if it is empty, so creating a session file fails with ENOSPC. The 5.9 million files in/var/lib/php/sessionsare the cause. - Deleted but open file:
ducounts directory entries, not blocks held by unlinked inodes.lsof +L1shows mysqld holding a deleted 7.8G file. Those blocks cannot be reused until the file descriptor is closed, which is whydfused, 13.9G, is much larger thandu, 6.1G. The arithmetic 6.1 plus 7.8 equals 13.9G.
So df -h alone is misleading twice: it looks healthy relative to the inode ceiling, and its used figure includes blocks that du cannot see. You need df -i and lsof +L1.
2. The team proposes rm -rf /var/lib/php/sessions/* first. Evaluate the risks and give a safer ordered remediation plan within 15 minutes.
Read the worked answer
rm -rf /var/lib/php/sessions/* is risky in this exact state. The shell expands * to 5.9 million pathnames before rm starts; that can exceed ARG_MAX and fail with 'Argument list too long'. Even if it starts, unlinking millions of files is metadata-heavy and can saturate the disk for far longer than 15 minutes, hurting the database.
Safer order:
- Stop the writer: disable the PHP-FPM pool or put the application in maintenance mode so new session files stop arriving.
- Free blocks fast: use
lsof +L1to get the mysqld PID and file descriptor, then truncate the still-open deleted file with: > /proc/PID/fd/FD, or restart mysqld in the window. That returns 7.8G immediately without deleting live data. - Free inodes in bounded batches:
find /var/lib/php/sessions -type f -mtime +1 -print0 | ionice -c3 xargs -0 -n 1000 rm -f. Start with the oldest files, which are least likely to be active sessions. Watchdf -iafter each batch. - Re-enable the writer once IFree is comfortably above zero.
Do not delete the directory itself; the application expects it to exist with the right ownership and mode.
3. What permanent configuration changes would you make, and why won't deleting files now fix the inode ceiling?
Read the worked answer
Deleting files raises IFree only until the workload creates more tiny files. The inode ceiling is fixed at mkfs time; ext4 cannot add inodes online. So prevention means either fewer files or a filesystem with more inodes.
Concrete changes:
- Re-enable PHP session garbage collection, or replace it with
systemd-tmpfiles: a configuration line such asd /var/lib/php/sessions 1733 root root 1dcleans files older than a day. Verify the mode matches the PHP pool's expectations. - Put
/var/lib/php/sessionson its own filesystem or a size-capped tmpfs, so a runaway session count cannot exhaust the/varinode pool. - Alert on
df -ias well asdf -h; a 69%-full filesystem with 0 free inodes is an outage. - If session volume is legitimately large, recreate the filesystem with a higher inode ratio (
mkfs.ext4 -i 8192or-N), but that is a planned outage, not a 15-minute fix. - Add a tmpfiles or logrotate rule for any other directory that accumulates one file per request.