Home Server & Self-Hosting

Filesystem Space on a Debian Home Server - Diagnose Blocks, Inodes, and Hidden Open Files

Filesystem Space on a Debian Home Server - Diagnose Blocks, Inodes, and Hidden Open Files

A service cannot append to its database, a backup stops halfway, or an application logs No space left on device. The obvious explanation is that the disk is full. That may be true, but it is not yet a useful diagnosis. Which mounted filesystem is affected? Has it exhausted data blocks or inodes? Is a running process still holding a deleted file? Or is the failure actually caused by a quota or a read-only filesystem?

A good response should answer those questions before deleting anything. On a home server, the safest sequence is closer to investigating a leaking pipe than emptying random cupboards: locate the pressure, stop the continuing flow, identify what can be removed by policy, and then verify that the dependent services still work.

“No space left” is a symptom, not a complete diagnosis

At the system-call level, Linux can return ENOSPC when the device containing a file has no room for more data. The Linux write(2) manual also distinguishes this from EDQUOT, which means that a user quota for disk blocks has been exhausted. Applications often simplify such errors in their logs, so begin with evidence from the host rather than the wording alone.

Capacity also belongs to a filesystem, not simply to “the disk.” A server may have free space on one mounted filesystem while /var, a database volume, or a backup mount is full. A write can therefore fail even when another device has hundreds of gigabytes available.

There are at least three common capacity questions worth separating:

  • Blocks: Is the filesystem out of space for file data and metadata?
  • Inodes: Can the filesystem still create another file or directory entry?
  • Referenced space: Has a file been removed from its directory while a process still keeps it open?

These paths can look similar to an application, but they require different remedies.

Start with the affected filesystem

The GNU Coreutils df documentation defines the tool as a report of used and available space on filesystems. Give it the path that failed rather than assuming the root filesystem is responsible:

df -h /path/that/failed
df -T /path/that/failed
df -i /path/that/failed

The first command shows human-readable block capacity for the filesystem containing the path. The second adds the filesystem type. The third replaces block statistics with inode statistics. Record the source device and mount point; subsequent investigation should stay focused on that filesystem.

Human-readable output is convenient for a person, but scripts should request explicit fields or stable units. More importantly, do not treat a displayed percentage as the whole story. Space available to an unprivileged service can differ from a simplistic total-minus-used calculation, and filesystem implementations have their own allocation and reservation details.

Check inodes even when bytes remain

An inode stores information about a filesystem object, including metadata and where its data is located. A workload that creates enormous numbers of tiny cache files, sessions, queue entries, or extracted package files may run out of available inodes before it runs out of bytes. In that case, deleting one large archive may free blocks but fail to solve the actual shortage.

If df -i points to inode pressure, GNU du can estimate which directory trees contain many filesystem objects:

sudo du --inodes -x --max-depth=1 /affected/mount
sudo du --inodes -x --max-depth=1 /affected/mount/suspect-directory

According to the GNU du manual, --inodes reports inode usage instead of block usage, while -x keeps traversal on one filesystem. Repeat the second command only for a directory that deserves closer inspection. A high count identifies where to look; it does not decide what is safe to delete.

Use du to locate growth, not to replace df

When blocks are scarce, inspect the top level of the affected mount:

sudo du -x -h --max-depth=1 /affected/mount
sudo du -x -h --max-depth=1 /affected/mount/suspect-directory

df and du answer different questions. df reports filesystem-level allocation. du estimates the space represented by files reachable under the selected directory tree. Their totals should not be expected to match perfectly.

The difference may be ordinary. Permissions can prevent a user from seeing parts of a tree. Hard links, sparse files, copy-on-write storage, compression, nested mounts, and filesystem metadata complicate accounting. This is why the commands above use sudo for visibility and -x to avoid silently walking into another mounted filesystem, but even then du remains an estimate.

A large gap can also be a valuable clue. If df says the filesystem is heavily allocated but reachable files reported by du explain much less, check for deleted files that remain open.

Find deleted files that a process still holds open

On Unix-like systems, removing a pathname does not necessarily release the underlying storage immediately. If a process still has the file open, it may continue using that file until the descriptor is closed. This often matters with a log file that was manually removed while its service kept writing.

sudo lsof +L1

The lsof(8) manual states that +L1 selects open files whose link count is less than one: open files that have been unlinked. Running with suitable privileges matters because a restricted view can omit files owned by other processes.

Read the process name, PID, file descriptor, size, and pathname before acting. Do not kill every listed process. A controlled restart of the specific owning service may close the descriptor and release the space, but that restart also has operational consequences. Check the service documentation, current work, and restart policy first. For a database or another stateful service, preserving integrity matters more than reclaiming space a few seconds sooner.

Inspect likely producers before cleaning them

Directory size is only the beginning of the question. The next question is why it grew. Common producers include application logs, system journals, caches, temporary exports, upload staging areas, database files, container layers, and backups. Each has a different owner and retention policy. A generic rm command cannot know which data is disposable.

For systemd journals, inspection is straightforward:

journalctl --disk-usage

The journalctl(1) documentation says this total includes active and archived journal files. Its vacuum operations remove archived journals, not active ones, so a requested vacuum limit does not guarantee that the displayed total will fall to exactly that value. Prefer deliberate retention settings and the service's supported cleanup mechanism over emergency manual deletion.

Before cleanup, capture enough context to explain the incident later: affected mount, block and inode availability, largest relevant directories, growing process, and recent service errors. Then stop or limit the producer if it is still expanding. Cleaning while an uncontrolled process continues to write merely buys an unknown amount of time.

A cautious recovery order

  1. Confirm the failing path and error. Avoid diagnosing a different mount because it is easier to inspect.
  2. Measure blocks and inodes. Save the relevant df output and note the mount point.
  3. Locate the responsible tree or hidden reference. Use bounded du passes and lsof +L1; do not start with a whole-server deletion search.
  4. Stop the growth. Pause the failed job, rate-limit incoming work, or perform a controlled service action where appropriate.
  5. Clean by ownership and policy. Use an application's retention command, log rotation, cache management, or an established backup policy.
  6. Re-measure and test. Check block and inode availability again, then verify the service that failed and any service that was restarted.
  7. Prevent recurrence. Adjust retention, capacity, workload behavior, or alerting based on the identified cause.

This order deliberately separates diagnosis from deletion. If the files are evidence of a runaway job or security incident, deleting first can erase the explanation. If they are the only current backup, freeing space can create a larger risk than the original outage.

Monitor both bytes and inodes

Periodic checks should cover both dimensions. The Prometheus Node Exporter guide, for example, documents filesystem metrics including space available to non-root users. Other monitoring stacks can collect equivalent values.

There is no honest universal warning percentage. A slowly changing archive and a busy database need different headroom. Ten percent may represent months on one volume and minutes on another. A better policy considers at least the rate of growth, time needed for a human to respond, the largest expected operation, and whether automatic cleanup is trustworthy.

Prometheus guidance recommends keeping alerts actionable, allowing slack for small blips, and treating approaching capacity as something that may require intervention. An alert should therefore identify the instance and mount, distinguish byte pressure from inode pressure, and point to a short runbook. An alarm that only says “disk high” invites guesswork at the worst moment.

Know when this runbook is not enough

Capacity is not the only reason writes fail. A quota can be exhausted even when the filesystem has room. A filesystem may be remounted read-only after errors. Permissions, file-size limits, storage faults, and network-filesystem behavior can produce other messages. The exact error in the application and system journal still matters.

Filesystem-specific tools may also report details that generic Coreutils cannot explain. If df, du, and open-file inspection do not reconcile the observations, resist the temptation to force a simple answer. Preserve the evidence and consult documentation for that filesystem and storage layer.

Conclusion

“No space left” is the beginning of an investigation. First locate the affected filesystem, then separate block pressure from inode pressure, compare filesystem accounting with the reachable file tree, and check whether a process retains deleted files. Only after identifying the owner should cleanup begin.

The deeper lesson is not a particular command. Capacity management works when observation, ownership, retention, and alerting form one loop. The useful question is not merely “What can be deleted now?” but “What grew, why did nobody have time to react, and what evidence would make the next response calmer?”

References