Conceptual editorial illustration
AI-generated editorial illustration. The measurements and experiment results are presented separately below.

Deleting a large log and seeing little change in free space is a reason to check what is still holding the data. It is also a reason to compare the right measurements: filesystem capacity, directory usage and a file's logical size answer different questions.

Disagreement between df and du is a documented historical community question. It has several possible explanations. This guide tests one specific mechanism: unlinking a file while a process still has it open. It does not diagnose every discrepancy or recommend deleting an active application's logs.

First identify which filesystem is constrained

df -hP /
df -iP /

These read-only GNU df commands show space and inode use for the filesystem containing /. Replace / with an existing path used by the failing application if its data lives on another mount. A root-filesystem report cannot establish the free space of a separate data volume.

  • Space: compare Avail and Use%. Human-readable units are rounded; they are useful for orientation, not an exact reconciliation.
  • Inodes: compare IFree and IUse%. On filesystems with a limited inode pool, many small files can exhaust file-creation capacity while byte capacity remains. Filesystems differ; an unavailable inode report is not evidence of zero use.
  • Errors: keep the application's actual error. A quota, a read-only mount, permissions or another constraint can prevent writes even when this filesystem has available space.

Compare directory totals within a defined scope

du -x -h --max-depth=1 /var

This GNU du example summarizes allocated usage under /var without descending into directories on other filesystems. Choose a directory on the affected filesystem. --max-depth=1 limits the summary lines, not the amount of recursive work needed to total them; scanning a large tree can still be expensive. We did not run this scan against the server's business directories.

Retain permission and read errors. An incomplete directory walk is not a complete account of the disk. A directory total is also not automatically the whole filesystem: other directories, filesystem metadata, reserved space, snapshots or mounts can affect the comparison.

By default, du measures allocated usage. A file's logical byte length is a different quantity; sparse files are one reason they differ. For exact units in our tiny fixture we used du -B1 -s, not a rounded human-readable total. We did not test sparse files, snapshots or inode exhaustion in this drill.

A deleted name can leave a live file behind

The Linux unlink manual distinguishes removing a name from releasing the file: if its last link is removed while a process still has it open, the file remains until the last referring file descriptor is closed.

On November 5, 2026, we wrote a new 1 MiB synthetic file in a disposable directory, kept our own descriptor open and removed only that file's name. The environment was Linux 6.8.0-147-generic, Python 3.12.3 and GNU coreutils du 9.4. No service log, existing database, daemon configuration or real application's descriptor was touched.

ObservationNamed, openUnlinked, open
Original pathname existsYesNo
Hard-link count10
Logical file bytes1,048,5761,048,576
Allocated file bytes1,048,5761,048,576
Fixture directory du bytes1,052,6724,096

The empty fixture directory already occupied 4,096 bytes in this environment. After unlinking, its directory walk returned to that baseline, while os.fstat() on the open descriptor still reported 1,048,576 allocated bytes. The process could read the original data, and its /proc/self/fd link was marked (deleted).

When the script closed its descriptor, that descriptor's proc entry disappeared and os.fstat() on its former number failed with EBADF. All eight defined checks passed. We did not measure a filesystem-wide df change: concurrent workloads and filesystem accounting would make that an unreliable way to attribute a tiny change on this shared host. The experiment demonstrates the open-file mechanism, not a full-disk recovery or block-reclamation timing benchmark.

Inspect ownership before choosing a recovery action

If lsof is already installed and you have permission to inspect the relevant processes, its manual documents this read-only filter for open files whose link count is below one:

lsof +L1

Use the process, descriptor and filesystem information to identify the owner. Rows are not necessarily distinct files: several descriptors or processes can refer to the same object, so adding every displayed size can overcount. Access restrictions can hide entries; no visible output is not proof that no process holds an unlinked file. We checked this option against the lsof manual; our isolated experiment used only its own proc descriptor instead of a system-wide lsof scan.

Once ownership is known, use the application's documented log-reopen or rotation procedure and verify its behavior. A restart may release a descriptor but also interrupts work; it is a separate operational decision. Avoid deleting more files or writing through another process's proc descriptor as a diagnosis shortcut.

If the growing files belong to Docker

Docker's logging documentation says its default json-file driver does not rotate logs by default, while the local driver rotates by default. Changing daemon logging defaults does not migrate existing containers. Check the container's actual logging configuration and plan any recreation with its data and availability requirements. This article does not change a Docker daemon, recreate a container or test a rotation policy.

Repeat the 1 MiB drill safely in a new directory

Download and inspect disk_unlinked_demo.py. It requires Linux with accessible /proc/self/fd, Python 3.9 or later and existing GNU du. It requires at least 64 MiB free at the output location as a small guard. It creates a fresh child directory, writes one 1 MiB file, unlinks only that synthetic name and closes its own descriptor. It makes no network requests and leaves its result JSON for inspection.

python3 disk_unlinked_demo.py --out ./disk-drill

Read result.json in the new disk-unlinked-... child directory. Compare linked, unlinked_while_open, after_close and the eight checks with our recorded result. Allocation and directory sizes can differ on another filesystem; the measured values above are not universal constants.

If your problem is backup growth, define the required recovery points before changing retention. Our SQLite restore drill shows why a file that exists is not yet evidence of the recovery state you need. For a job repeatedly producing output, use the automation checklist to bound the work and record its result.

Sources and limits

This was one local Linux filesystem and one synthetic foreground process. It does not establish the cause of your disk usage, measure Docker log rotation, fill a disk, exhaust inodes, test quotas or validate recovery of a production application.