Linux df Not Showing Correct Free Space After File Deletion: Causes and Solutions for Persistent Storage Issues


4 views

If you're managing Linux file servers with frequently deleted files, you might encounter this puzzling scenario:

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       100G   99G     0  99% /data

$ rm -rf /data/large_folder/*

$ df -h  # After deletion
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       100G   99G     0  99% /data

This behavior typically occurs when:

  • Deleted files are still held open by running processes
  • You're using NFS or other network filesystems with delayed updates
  • The filesystem uses noatime or similar mount options
  • Files are being deleted from snapshots or LVM volumes

First, identify which processes are holding deleted files open:

# Method 1: Using lsof
sudo lsof +L1 | grep deleted

# Method 2: Checking /proc
sudo find /proc/*/fd -ls | grep '(deleted)'

# Example output:
# 12345  0 l-wx------ 1 root root 64 Aug 10 15:30 /proc/5678/fd/4 -> '/data/logs/app.log (deleted)'

Case 1: Files Held by Running Processes

For servers running long-lived processes (like web servers or databases):

# Option A: Restart the holding process
sudo systemctl restart nginx

# Option B: Truncate the file if you can't restart
sudo truncate -s 0 /proc/5678/fd/4

Case 2: NFS or Network Filesystems

For network-mounted storage with delayed updates:

# Force NFS to sync
sudo sync
sudo exportfs -ra

# Alternative: Use du instead of df
sudo du -sh /data --exclude="./.snapshot"

Case 3: LVM or Snapshot Issues

For LVM thin provisioning or snapshot scenarios:

# Check LVM status
sudo lvs -a -o+devices

# Free space from snapshots
sudo lvremove /dev/vg00/snapshot_old

# Reclaim thin pool space
sudo lvchange --discards passdown /dev/vg00/tpool

Implement these in your server setup:

# /etc/fstab entry with proper options
/dev/sda1 /data ext4 defaults,noatime,discard 0 2

# Cron job to regularly check for deleted files
0 * * * * root /usr/bin/lsof +L1 | grep deleted | mail -s "Deleted files report" admin@example.com

Instead of relying solely on df, consider this Python script for accurate space monitoring:

#!/usr/bin/env python3
import os
import subprocess

def get_real_usage(path):
    # Get actual disk usage excluding deleted files
    du = subprocess.check_output(['du','-s', path]).split()[0].decode()
    df = subprocess.check_output(['df', path]).decode().split('\n')[1]
    total = df.split()[1]
    return f"Real usage: {du} of {total}"

print(get_real_usage('/data'))

Many Linux sysadmins have encountered this frustrating scenario: you delete large files to free up space, but df -h stubbornly shows the same used percentage. This isn't just an academic concern - when monitoring scripts rely on df output to manage storage, incorrect reporting can break automated workflows.

The root cause typically involves processes keeping deleted files open. In Linux, when a file is deleted while being held open by a process, the space isn't truly freed until all handles are closed. The noatime mount option you mentioned doesn't directly affect this behavior, but it's good practice for reducing filesystem metadata writes.

First, verify the discrepancy between what df reports and actual available space:


# Check filesystem usage
df -h /mnt/data

# Compare with actual available space
sudo lsof +L1 | grep deleted
sudo lsof /mnt/data | grep deleted

If you see output listing deleted files, these are the culprits holding your disk space hostage.

Here are three approaches to resolve this:

1. Restarting Owning Processes

The cleanest solution is to identify and restart processes holding open handles:


# Find processes with deleted files
sudo lsof -nP +L1 | awk '$NF=="(deleted)" {print $2}' | sort -u

# Then gracefully restart relevant services
sudo systemctl restart nginx

2. Alternative Space Monitoring

For scripts that monitor disk space, consider using du instead of df for more accurate results:


# Get actual used space excluding deleted files
du -sh /mnt/data/

3. Automated Cleanup Script

Here's a sample Python script to monitor and alert about stuck deleted files:


import subprocess
import re

def check_deleted_files(mount_point):
    cmd = f"sudo lsof -F pn +L1 {mount_point}"
    output = subprocess.getoutput(cmd)
    
    deleted_files = []
    current_pid = None
    
    for line in output.split('\n'):
        if line.startswith('p'):
            current_pid = line[1:]
        elif line.startswith('n') and '(deleted)' in line:
            deleted_files.append((current_pid, line[1:]))
    
    return deleted_files

if __name__ == "__main__":
    mount = "/mnt/data"
    problematic_files = check_deleted_files(mount)
    
    if problematic_files:
        print(f"Warning: {len(problematic_files)} deleted files still held open")
        for pid, path in problematic_files:
            print(f"PID {pid}: {path}")

To avoid this issue in the future:

  • Implement proper file handle management in your applications
  • Consider using fallocate instead of actual files for temporary storage
  • Set up monitoring for processes that frequently hold deleted files open

In extreme cases where you can't restart critical processes, you might need to:


# Find largest deleted files
sudo lsof -nP +L1 | awk '$NF=="(deleted)" {print $7, $2, $1}' | sort -n | tail

# Then clear space by truncating the largest file
sudo truncate -s 0 /proc/1234/fd/15

Note that this last approach should be used cautiously as it may cause application instability.