Linux df Command Deep Dive: From Disk Space Monitoring to inode Exhaustion Troubleshooting#

Server disk full, SSH login fails with No space left on device. First reaction: df -h. But sometimes there’s still space showing, yet errors persist—that’s inode exhaustion. After diving deep into the df command, I found this seemingly simple tool hides quite a few intricacies.

df’s Underlying Implementation: statvfs System Call#

The core data of df comes from the statvfs() system call, which returns filesystem statistics:

#include <sys/statvfs.h>

int statvfs(const char *path, struct statvfs *buf);

struct statvfs {
  unsigned long f_bsize;    // Filesystem block size
  fsblkcnt_t    f_blocks;   // Total blocks
  fsblkcnt_t    f_bfree;    // Free blocks
  fsblkcnt_t    f_bavail;   // Available blocks for unprivileged users
  fsfilcnt_t    f_files;    // Total inodes
  fsfilcnt_t    f_ffree;    // Free inodes
  // ...
};

The key distinction is between f_bfree and f_bavail:

  • f_bfree: Actual free blocks in the filesystem
  • f_bavail: Blocks available to ordinary users (5% reserved for root)

This is why df -h can show Use% exceeding 100%—root can still write using that reserved 5%.

Common Parameters Explained#

-h: Human-Readable Format#

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       100G   85G   10G  90% /
tmpfs           7.8G     0  7.8G   0% /dev/shm

-h automatically selects appropriate units (K/M/G/T), much more readable than the default 1K blocks.

-i: inode Usage#

$ df -i
Filesystem      Inodes  IUsed  IFree IUse% Mounted on
/dev/sda1       655360 655350     10  100% /

This is the classic sign of inode exhaustion: Use% is only 90%, but IUse% is 100%. Too many small files (like log rotations, cache files) consume inodes even when disk space is still available.

-T: Display Filesystem Type#

$ df -T
Filesystem     Type     1K-blocks   Used Available Use% Mounted on
/dev/sda1      ext4     104755200 85000000  10000000  90% /
/dev/sdb1      xfs      209715200 50000000 159715200  20% /data

Different filesystem characteristics:

Filesystem Max File Size Max Volume Size Features
ext4 16TB 1EB Mature, stable, Linux default
xfs 8EB 8EB High concurrency, large files
btrfs 16EB 16EB Snapshots, compression, checksums
tmpfs Memory limit Memory limit RAM filesystem, fast

–total: Summary Statistics#

$ df -h --total
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       100G   85G   10G  90% /
/dev/sdb1       200G   50G  150G  20% /data
total           300G  135G  160G  45% -

Useful for quick total capacity assessment.

Practical Scenarios#

1. Disk Space Alert Script#

#!/bin/bash
# Monitor disk usage, send email alert when threshold exceeded

THRESHOLD=80

df -H | grep -vE '^Filesystem|tmpfs|cdrom' | while read line; do
    usage=$(echo "$line" | awk '{print $5}' | sed 's/%//')
    mount=$(echo "$line" | awk '{print $NF}')

    if [ "$usage" -gt "$THRESHOLD" ]; then
        echo "Warning: $mount usage at ${usage}%" | mail -s "Disk Space Alert" admin@example.com
    fi
done

2. inode Exhaustion Investigation#

# Find which directory has the most inodes
for dir in /*; do
    if [ -d "$dir" ]; then
        count=$(find "$dir" -xdev | wc -l)
        echo "$count $dir"
    fi
done | sort -rn | head -10

Usually /var/spool/postfix/maildrop or /tmp accumulates many small files.

3. Combine with du to Locate Large Directories#

# First check which partition is full with df
df -h

# Then locate large directories with du
du -h --max-depth=1 / | sort -hr | head -10

4. View Real Mount Point Usage#

# -x exclude specific filesystem types
df -h -x tmpfs -x devtmpfs

# -t show only specified types
df -h -t ext4 -t xfs

Performance Considerations#

The df command itself is rarely a performance concern, but special scenarios require attention:

NFS Mount Timeout#

# When NFS server is down, df will hang
# Solution: use timeout or background execution
timeout 5 df -h /mnt/nfs

Large Number of Mount Points#

# View all mount points (including docker, snap, etc.)
$ df -h | wc -l
127

Long-running systems accumulate many mount points, and df queries each one sequentially.

Common Pitfalls#

1. Space Not Released After File Deletion#

$ df -h /data
/dev/sdb1       200G  180G   20G  90% /data

$ rm -f /data/large_file
$ df -h /data
/dev/sdb1       200G  180G   20G  90% /data  # Space unchanged!

Reason: File is held by a process; deletion only marks it as pending removal.

# Find processes holding deleted files
$ lsof +L1
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx    1234 www-data   3w   REG  8,17  50G  123 /data/large_file (deleted)

# Restart nginx to release space
$ systemctl reload nginx

2. Reserved Space Misunderstanding#

$ df -h /
/dev/sda1       100G   95G    0G  95% /

# Regular user write fails
$ touch /test
touch: cannot touch '/test': No space left on device

# Root user can still write
# sudo touch /test  # Success

ext4 reserves 5% by default for root. For large disks (like 10TB), you can reduce it:

# Reduce to 1%
tune2fs -m 1 /dev/sda1

3. tmpfs Memory Consumption#

$ df -h /dev/shm
tmpfs           7.8G     0  7.8G   0% /dev/shm

tmpfs uses RAM; writing large files can exhaust memory. Production environments should limit size:

mount -o remount,size=2G /dev/shm

Web Implementation: Browser-Based Disk Monitoring#

While browsers can’t directly access local disks, we can implement a “virtual disk” concept using Web APIs:

// Use Storage API to monitor localStorage quota
async function checkStorageQuota() {
  const estimate = await navigator.storage.estimate()
  const usedMB = (estimate.usage || 0) / 1024 / 1024
  const quotaMB = (estimate.quota || 0) / 1024 / 1024
  const percent = ((estimate.usage || 0) / (estimate.quota || 1)) * 100

  return {
    used: usedMB.toFixed(2) + ' MB',
    quota: quotaMB.toFixed(2) + ' MB',
    percent: percent.toFixed(1) + '%',
    warning: percent > 80
  }
}

// Simulate df -h output
function formatDiskInfo(info: DiskInfo): string {
  const pad = (str: string, len: number) => str.padEnd(len)
  return [
    pad('Filesystem', 20),
    pad('Size', 10),
    pad('Used', 10),
    pad('Avail', 10),
    pad('Use%', 6),
    'Mounted on'
  ].join(' ') + '\n' +
  [
    pad(info.fs, 20),
    pad(info.total, 10),
    pad(info.used, 10),
    pad(info.avail, 10),
    pad(info.percent, 6),
    info.mount
  ].join(' ')
}

After diving deep into the df command, I found it’s more than just a simple disk viewer. From inode exhaustion to reserved space, from NFS timeouts to file handles, each scenario has its troubleshooting approach. Next time you encounter disk issues, don’t just check Use%—remember to check -i too.

Online disk space calculator: Disk Space Monitor

Related tools: Memory Unit Converter | Port Checker