Runbook: Filesystem full - blocks, inodes, and deleted-but-open files
1 · Prerequisites
Confirm every item is in place before any state change.
2 · Pre-checks
Read-only diagnostic commands. If any of these don't match expected output, stop and investigate further.
- · Identify the filesystem that is actually full with df -h and df -i - not the directory someone mentioned
- · Confirm whether it is blocks or inodes; they have different fixes
- · Confirm whether the host is a cluster node before restarting anything
- · Confirm what the filesystem holds: logs, database, application data, or root
- · Note the current free space so you can prove the reclaim worked
3 · Procedure
Execute each step in order. Verify the expected output of a step before moving to the next.
- 1df -h and df -i on every mount, to find the real culprit
- 2Check for deleted-but-open files with lsof +L1 before deleting anything
- 3If inodes are exhausted, find the directory with the file count, not the byte count
- 4If blocks are exhausted, find the largest consumers with du, bounded to one filesystem
- 5Reclaim in the safe order: rotate and vacuum logs, clear caches, then archive
- 6Restart only the specific process holding deleted files, if that was the cause
- 7Verify free space and free inodes have actually recovered
- 8Fix the cause: rotation policy, retention, quota, or monitoring threshold
4 · Verification
Confirm the procedure actually fixed the problem.
- ✓df -h shows the mount below its alert threshold
- ✓df -i shows inode use below its alert threshold
- ✓lsof +L1 on that filesystem returns nothing significant
- ✓The affected service writes successfully again (a real write test, not just is-active)
- ✓The monitoring alert has cleared on its own, not been silenced
5 · Rollback
If verification fails, undo the procedure in reverse order.
- ↶Restore any file deleted in error from backup or snapshot - list what you deleted before you delete it
- ↶If a log rotation change caused a service to lose its log handle, restart that service
- ↶If a filesystem was grown, an LVM/filesystem grow is not reversible; roll back at the volume-manager level only from a snapshot
6 · Escalation
When the runbook isn't enough, contact:
- · The full filesystem is a database volume: escalate to the data team before deleting anything
- · Space is consumed by a process you cannot identify or stop: escalate to the application owner
- · The filesystem is full again within minutes of reclaiming: escalate; you have a runaway writer, not a capacity problem
- · Root filesystem full on a cluster node with resources running: escalate to the cluster on-call before restarting services
“Disk full” is three different incidents wearing the same
alert. Blocks exhausted, inodes exhausted, and space held by
deleted-but-still-open files each look like No space left on device and each has a different fix. Getting the
classification wrong is how an operator spends twenty
minutes deleting files that free nothing.
Step 1: Blocks or inodes?
Run both. Always both.
df -h
df -i
# Local filesystems only - skip NFS, which can hang when the server is down
df -hlT -x tmpfs -x devtmpfsdf -hat 100% anddf -ilow → block exhaustion. Something large. Go to Step 3.df -hwith free space butdf -iat 100% → inode exhaustion. Something numerous. Go to Step 4.- Both look fine but writes still fail → deleted-but-open files, or you are looking at the wrong mount. Go to Step 2.
Step 2: Deleted-but-open files
Check this before you delete anything. It is the fastest fix in this runbook and the one most often missed.
# Files with link count < 1: unlinked but still held open
sudo lsof +L1
# Narrow to the affected filesystem and sort by size
sudo lsof +L1 /var | awk 'NR==1 || $7 > 100000000'
# The gap that confirms it
sudo du -sxh /var
df -h /varThe typical cause is a log file rotated or rm-ed while the
daemon still holds it open. The daemon keeps writing to an
inode with no name, and the space is never returned until
that file descriptor closes.
# Preferred: tell the daemon to reopen its logs (no restart, no lost data)
sudo systemctl reload rsyslog
sudo systemctl kill -s HUP nginx.service
# If the daemon has no reopen path, truncate the descriptor in place.
# This frees the blocks immediately and keeps the process running.
# 1234 is the PID, 5 is the fd number, both from the lsof output.
sudo truncate -s 0 /proc/1234/fd/5
# Last resort: restart the process holding it
sudo systemctl restart <unit>Step 3: Block exhaustion - find the size
# Top-level offenders, bounded to this filesystem
sudo du -xh --max-depth=1 / 2>/dev/null | sort -rh | head -20
# Then descend into the winner
sudo du -xh --max-depth=1 /var 2>/dev/null | sort -rh | head -20
# The 20 largest individual files on this filesystem
sudo find / -xdev -type f -printf '%s %p\n' 2>/dev/null \
| sort -rn | head -20 | numfmt --field=1 --to=iec-xdev and -x matter. Without them the walk crosses into
/proc, /sys, network mounts and other filesystems, and
you spend minutes measuring things that are not full.
Reclaim in order of least risk:
# 1. Journal - bounded, reversible only from backup
sudo journalctl --disk-usage
sudo journalctl --vacuum-size=500M
sudo journalctl --vacuum-time=7d
# 2. Package caches - always safe to rebuild
sudo apt-get clean # Debian/Ubuntu
sudo dnf clean all # RHEL family
# 3. Rotated logs older than the retention policy
sudo find /var/log -type f -name '*.gz' -mtime +14 -print # LOOK FIRST
sudo find /var/log -type f -name '*.gz' -mtime +14 -delete
# 4. Old kernels (Debian) - keep at least the running one and one spare
uname -r
sudo apt-get --purge autoremoveStep 4: Inode exhaustion - find the count
Inodes run out when something creates millions of small files: a session directory, a mail spool, a cache with no eviction, a failing job writing one file per retry.
df -i
# Which top-level directory holds the file count
sudo du -x --inodes --max-depth=2 / 2>/dev/null | sort -rn | head -20
# Portable equivalent if du lacks --inodes
for d in /var/*; do printf '%8s %s\n' \
"$(sudo find "$d" -xdev -printf . 2>/dev/null | wc -c)" "$d"; done | sort -rn | headCommon inode sinks, in the order they usually turn up:
/var/spool/postfix, PHP or application session
directories, /var/lib/php/sessions, unrotated
per-request log files, .git objects in a runaway CI
checkout, and Docker/Podman overlay layers.
# Look first
sudo find /var/lib/php/sessions -type f -mtime +2 | wc -l
# Then delete, in batches so the shell does not choke on argument length
sudo find /var/lib/php/sessions -type f -mtime +2 -delete
# Mail spool - inspect before touching; queued mail is data
sudo postqueue -p | tail -1Step 5: The emergency reserve
Every ext filesystem reserves 5% of blocks for root by default. That is why a filesystem at “100%” often still lets root write while unprivileged services fail — and it is the headroom that lets you run these commands at all.
sudo tune2fs -l /dev/mapper/vg0-var | grep -E 'Reserved block count|Block count|Free blocks'Lowering the reserve buys minutes and costs you the safety margin you are currently standing on. Treat it as a last-resort action taken knowingly, not a routine reclaim:
# Only on a data filesystem, never on / - and reverse it afterwards
sudo tune2fs -m 1 /dev/mapper/vg0-varStep 6: Verify, then fix the cause
df -h /var
df -i /var
sudo lsof +L1 /var | wc -l
# A real write test as the service account
sudo -u www-data sh -c 'echo ok > /var/www/.writetest && rm /var/www/.writetest' \
&& echo 'write OK'Reclaiming space is not the fix. The fix is one of:
- Rotation. A
logrotaterule withsizeordailyplusrotate N, andcopytruncateor apostrotatereopen so the daemon does not hold the deleted file. - Retention.
SystemMaxUse=in/etc/systemd/journald.conf, application-level log retention, cache eviction. - Quota. A per-user or per-project quota so one writer cannot consume the whole filesystem.
- Monitoring. Alert on the trend, not the threshold. A filesystem that fills at 4 GB/day should page you three days out, not at 95%.
# /etc/systemd/journald.conf
# SystemMaxUse=1G
# SystemKeepFree=2G
sudo systemctl restart systemd-journald
journalctl --disk-usageCommon patterns
| Symptom | Likely cause | Resolution |
|---|---|---|
df 100%, du much smaller | Deleted-but-open files | lsof +L1, then reload or truncate the fd |
df has space, writes fail | Inodes exhausted | df -i, find the file-count sink |
| Space returns then vanishes in minutes | Runaway writer | Find and stop the writer; do not keep reclaiming |
| Root can write, service cannot | 5% reserved blocks | You are at real 100%; reclaim now |
/var/log huge after an incident | Debug logging left on | Turn it off; that is the cause |
Only /boot full | Old kernels accumulated | Purge old kernels, keep running + one |
| Deleting files frees nothing | Snapshot or open fd holds the blocks | Check snapshots and lsof +L1 |
Knowledge check
Knowledge check · 4 questions
Q1. `df -h /var` reports 100% used. `du -sxh /var` reports 12 GB on a 40 GB filesystem. You delete another 2 GB of logs and df still reports 100%. What is happening?
Q2. A service reports "No space left on device" but `df -h` shows 60% free on that mount. What do you check next?
Q3. You can add inodes to an existing ext4 filesystem without recreating it.
Q4. Which actions are appropriate first responses to a full /var on a production host? Select all that apply.
Passing score: 75%. Answers are checked in this browser.