skip to content

df, du & duf — Disk Usage

Check filesystem free space (df), measure directory sizes (du), and view a colourful disk overview (duf). Covers all key flags, human-readable output, modern alternatives (dust, gdu, ncdu), and common sysadmin recipes.

16 min read 45 snippets deep dive

df, du & duf — Disk Usage#

What it is#

df and du are POSIX standard disk-reporting utilities present on every Unix and Linux system: df shows free and used space per mounted filesystem, while du measures the disk space consumed by files and directories. duf is a modern, open-source Go-based alternative (v0.9.1, Sept 2025) maintained on GitHub that presents the same information in a colourful, human-friendly table. Reach for df when you need a quick filesystem-level view, du when hunting for large directories, duf when you want an at-a-glance overview of all mounts, and the newer dust / gdu / ncdu alternatives covered below when you need visual tree output or interactive cleanup.


df — Disk Free (filesystem summary)#

df reports the amount of disk space used and available on every mounted filesystem.

Common flags#

FlagMeaning
-hHuman-readable sizes (K, M, G)
-HHuman-readable, powers of 1000 (not 1024)
-TShow filesystem type
-t TYPEShow only filesystems of TYPE
-x TYPEExclude filesystem type
-iShow inode usage instead of blocks
-lOnly local filesystems
--totalAdd grand total row
df -h                    # all mounts, human-readable

Output:

Filesystem       Size  Used Avail Use% Mounted on
/dev/sda1         50G   18G   30G  38% /
/dev/sda2        200G   82G  108G  44% /home
tmpfs            2.0G  1.2M  2.0G   1% /tmp
tmpfs            2.0G     0  2.0G   0% /dev/shm
df -hT                   # include filesystem type

Output:

Filesystem     Type   Size  Used Avail Use% Mounted on
/dev/sda1      ext4    50G   18G   30G  38% /
/dev/sda2      ext4   200G   82G  108G  44% /home
tmpfs          tmpfs  2.0G  1.2M  2.0G   1% /tmp
tmpfs          tmpfs  2.0G     0  2.0G   0% /dev/shm
df -h /home              # specific mount point
df -h /dev/sda1          # specific device
df -i                    # inode usage (when "no space" but df -h looks fine)

Output:

Filesystem      Inodes  IUsed   IFree IUse% Mounted on
/dev/sda1      3276800 187432 3089368    6% /
/dev/sda2     13107200 412810 12694390    4% /home
tmpfs           511910    612  511298    1% /tmp
df -h -t ext4            # only ext4 filesystems
df -h -x tmpfs -x devtmpfs  # skip tmpfs/devtmpfs
df -h --total            # with grand total

Output:

Filesystem       Size  Used Avail Use% Mounted on
/dev/sda1         50G   18G   30G  38% /
/dev/sda2        200G   82G  108G  44% /home
total            250G  100G  138G  42%

Interpreting output#

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   18G   30G  38% /
tmpfs           2.0G  100M  1.9G   5% /dev/shm
  • Use% above ~85% → time to investigate or expand
  • Avail = 0 with non-zero Used → filesystem full
# Alert when any filesystem exceeds 90%
df -h | awk 'NR>1 && $5+0 > 90 {print "WARN:", $6, "is", $5, "full"}'

Output:

WARN: /var/log is 93% full
WARN: /boot is 91% full

du — Disk Usage (per directory/file)#

du estimates file space usage — the sum of blocks used by each file.

Common flags#

FlagMeaning
-hHuman-readable
-sSummary (total for each argument)
-cGrand total at the end
-d N / --max-depth=NDescend at most N levels
-aShow all files, not just directories
-xStay on one filesystem
--exclude=PATTERNSkip matching files/dirs
-lCount hard-linked files each time
--apparent-sizeActual file size (not disk blocks)
-bByte counts
--timeShow last modification time
du -sh *                  # size of each item in cwd

Output:

4.2G    Documents
1.8G    Downloads
512M    Music
 48M    Pictures
 12K    notes.txt
 80M    projects
du -sh /var/log           # total size of /var/log
du -sh ~/Downloads/*      # size of each download
du -h --max-depth=1 /     # one level deep from root

Output:

 15M    /bin
4.5G    /home
512M    /lib
 48M    /opt
3.2G    /usr
1.1G    /var
du -h -d 2 /home          # two levels from /home
du -ahc /etc/*.conf       # all .conf files + total

# Sort by size (largest first)
du -sh * | sort -rh | head -20

Output:

4.2G    Documents
 80M    projects
 48M    Pictures
1.8G    Downloads
512M    Music
 12K    notes.txt
# Find the biggest directories anywhere under /var
du -h /var | sort -rh | head -20

# Exclude patterns
du -sh --exclude=".git" --exclude="node_modules" .

Output: (none — exits 0 on success)

Find what’s eating space#

# Step-by-step drill-down
du -h --max-depth=1 /         # find big top-level dirs

Output:

 15M    /bin
4.5G    /home
512M    /lib
 48M    /opt
3.2G    /usr
1.1G    /var
 12G    /
du -h --max-depth=1 /var      # drill into /var
du -h --max-depth=1 /var/log  # drill into logs

# Top 20 largest files anywhere on the filesystem
find / -xdev -type f -printf '%s\t%p\n' 2>/dev/null \
  | sort -rn | numfmt --to=iec-i --suffix=B --field=1 | head -20

# Largest files under current dir
du -ah . | sort -rh | head -20

Output: (none — exits 0 on success)

Watch for growth#

# Compare directory size over time
du -sh /var/log > /tmp/before.txt
sleep 3600
du -sh /var/log > /tmp/after.txt
diff /tmp/before.txt /tmp/after.txt

Output: (none — exits 0 on success)


duf — Disk Usage/Free Utility#

duf is a modern, colourful replacement for df with better layout and filtering, written in Go by Christian Muehlhaeuser. Reach for it when you want a single command that shows local, network, and FUSE mounts side-by-side with sortable columns and JSON output for scripting. The current release is v0.9.1 (September 2025); the project is in maintenance mode but still ships occasional bug-fix releases.

Installation#

sudo apt install duf        # Debian/Ubuntu (20.10+)
brew install duf            # macOS
scoop install duf           # Windows
# or download a release binary from: github.com/muesli/duf/releases

Output: (none — exits 0 on success)

Usage#

duf                         # all mounts, colourful table
duf /home /tmp              # specific paths
duf --only local            # only local filesystems
duf --only network          # only network mounts
duf --only fuse             # only FUSE mounts
duf --hide-fs tmpfs,devtmpfs  # hide specific filesystem types
duf --inodes                # show inode usage
duf --output mountpoint,size,used,avail,usage  # custom columns
duf --sort size             # sort by size
duf --sort usage            # sort by usage %
duf --json                  # machine-readable JSON
duf --theme light           # light theme
duf --no-color              # disable colour
duf --width 100             # set terminal width
duf --all                   # include special/pseudo filesystems

Output: (none — exits 0 on success)

Available columns for --output#

mountpoint, size, used, avail, usage, type, filesystem, inodes, inodes_used, inodes_avail, inodes_usage


Combining them#

# Full disk health check
echo "=== Filesystem Usage ===" && df -hT
echo "=== Top Dirs Under /var ===" && du -h --max-depth=2 /var | sort -rh | head -10
echo "=== Inode Usage ===" && df -i | awk '$5+0 > 50'

# Find and delete old core dumps eating space
find / -xdev -name "core" -type f -size +100M -print0 | xargs -0 ls -lh

Output: (none — exits 0 on success)

-H vs -h: 1000 vs 1024#

df -h and du -h use powers of 1024 (KiB, MiB, GiB) but label them with the SI-looking abbreviations K, M, G. -H (capital) switches to powers of 1000, which matches the marketing capacity printed on drive labels. Use -H when reconciling a df figure against a manufacturer’s spec sheet, and -h everywhere else.

df -h /                # 50G = 50 × 1024^3 bytes ≈ 53.7 billion bytes
df -H /                # 53G = 53 × 1000^3 bytes ≈ 53.7 billion bytes

Output (df -h /):

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   18G   30G  38% /

Output (df -H /):

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        54G   20G   33G  38% /

Inode exhaustion debugging#

A filesystem can refuse new file creation while df -h still shows abundant free space — the symptom is “No space left on device” on touch, mkdir, or cp. The culprit is inode exhaustion: every file consumes one inode, and ext-family filesystems fix the inode count at mkfs time. Run df -i to see inode usage as a parallel “Use%” figure.

df -ih                       # human-readable inode usage

Output:

Filesystem     Inodes IUsed IFree IUse% Mounted on
/dev/sda1        3.1M  187K  2.9M    6% /
/dev/sda2       12.5M  413K   12M    4% /home
/dev/sdb1        488K  488K     0  100% /var/cache  ← inode-full
# Find directories with millions of small files
sudo find /var -xdev -type d -print0 \
  | xargs -0 -I{} sh -c 'echo "$(find "{}" -maxdepth 1 | wc -l) {}"' \
  | sort -rn | head -10

# Common offenders: session caches, mail spools, Maildir, npm caches
sudo find /var/cache -xdev -type d -links +2 -printf '%p\n' \
  | xargs -I{} sh -c 'echo "$(ls -A1 "{}" 2>/dev/null | wc -l) {}"' \
  | sort -rn | head

Output (sample):

3142187 /var/cache/php/sessions
   8412 /home/alice/.npm/_cacache/content-v2
   2103 /var/spool/postfix/maildrop

df vs du discrepancy#

df reads filesystem allocation tables; du walks the directory tree and sums file sizes. They diverge when the filesystem allocates space that the directory tree no longer references — most commonly because a running process is holding a file descriptor to a file that’s been unlinked. The blocks stay allocated until the process closes the fd or exits.

# Compare df and du for /var
df -h /var
du -sh /var

# If df reports much more used than du, hunt for deleted-but-open files
sudo lsof -nP +L1 | head -20                # files with link-count 0
sudo lsof | awk '/deleted/ {print $1, $2, $7, $NF}' | sort -rk3 | head

Output (lsof | grep deleted):

nginx     1234   /var/log/nginx/error.log.1 (deleted)  4.2G
postgres  5678   /var/lib/postgresql/wal/000001 (deleted)  1.8G
# Free the space without restart: truncate via /proc
sudo : > "/proc/1234/fd/3"        # if fd 3 is the deleted log

# Or restart the holding process
sudo systemctl restart nginx

Output: (none — exits 0 on success)

See also: lsof | grep deleted in ps, netstat & lsof.

—apparent-size vs allocated blocks#

By default du reports the disk blocks consumed, which is always a multiple of the filesystem block size (usually 4 KiB). --apparent-size reports the file’s logical byte length, which is smaller for sparse files (e.g. VM images, qcow2, large database files with holes) and larger if the file has a tiny payload that still occupies a full block.

du -sh image.qcow2                # 4.2G — what's on disk
du -sh --apparent-size image.qcow2 # 40G — virtual size (mostly holes)

# Many tiny files: apparent-size is much smaller
du -sb -d 1 /etc                  # bytes allocated
du -sb --apparent-size -d 1 /etc   # bytes in payload

Output: (none — exits 0 on success)

Sorting du output safely#

sort -h (human-numeric) understands K, M, G suffixes, but only GNU sort supports it — BSD/macOS users need sort -n after stripping suffixes, or gsort -h from coreutils. The pattern below is the safest cross-platform way to find the biggest entries in a directory.

# GNU systems
du -sh ./* | sort -rh | head -10

# macOS / BSD (use gnu-coreutils or numfmt)
du -sk ./* | sort -rn | head -10 | numfmt --field=1 --from-unit=1024 --to=iec

# Top 20 biggest files anywhere under cwd
du -ah . 2>/dev/null | sort -rh | head -20

# Biggest dirs at exactly depth 2
du -h --max-depth=2 / 2>/dev/null | sort -rh | head -20

Output:

4.2G    ./Videos
1.8G    ./Downloads/iso
512M    ./Music
 80M    ./projects
 48M    ./Pictures

ncdu — interactive du#

ncdu is an interactive, curses-based du viewer that lets you navigate a directory tree, sort by size, and delete entries inline. Reach for it when you need to find and remove large content rather than just measuring it — the keyboard-driven UI is much faster than repeated du --max-depth runs.

sudo apt install ncdu        # Debian/Ubuntu
brew install ncdu            # macOS

ncdu /                       # scan and browse from root
ncdu -x /                    # stay on one filesystem
ncdu -o scan.json /var       # save scan to a JSON file
ncdu -f scan.json            # browse a previous scan offline
ncdu --exclude '.git' .      # skip patterns

Output (interactive — sample first screen):

--- / ----------------------------------
  4.5 GiB [##########] /home
  3.2 GiB [#######   ] /usr
  1.1 GiB [##        ] /var
  512 MiB [#         ] /lib
   48 MiB [          ] /opt
   15 MiB [          ] /bin

Keys: d delete, n sort by name, s sort by size, g toggle percent/graph, ? help, q quit.

dust — visual du in Rust#

dust is a Rust-based du replacement (v1.2.4, January 2026) that renders a colour bar-chart tree of the largest subdirectories up to the terminal height — no | sort -rh | head pipeline required. Reach for it when you want a one-shot “what’s eating my disk?” view without launching an interactive TUI; it respects .gitignore, sorts largest-first by default, and is noticeably faster than du on large trees because it parallelises the walk.

brew install dust            # macOS / Linuxbrew
cargo install du-dust        # any platform with Rust
sudo apt install du-dust     # Debian 12+ / Ubuntu 23.10+

dust                         # tree of current dir
dust /var                    # tree under /var
dust -d 3                    # cap depth at 3
dust -n 30                   # show top 30 entries
dust -r                      # reverse: smallest first
dust -s                      # show apparent sizes
dust -x                      # stay on one filesystem
dust -i                      # ignore .gitignore'd files (default respects it)
dust -t 4                    # use 4 threads
dust -P                      # print full paths

Output (top-of-tree sample):

  4.5G  ┌── home         │█████████             │ 36%
  3.2G  ├── usr          │██████                │ 26%
  1.1G  ├── var          │██                    │  9%
  512M  └── lib          │█                     │  4%
 12.4G  /                │██████████████████████│100%

gdu — Go du with TUI and CLI modes#

gdu is a Go-rewrite of du that runs in either an interactive ncurses-style TUI (similar to ncdu) or a one-shot non-interactive mode. It parallelises the directory walk and is typically the fastest of the alternatives on NVMe storage. Reach for it when you want ncdu’s navigation but with throughput closer to native du, or when you need a non-interactive output for piping.

brew install gdu             # macOS / Linuxbrew
sudo apt install gdu         # Debian 12+ / Ubuntu 23.10+

gdu                          # interactive TUI in cwd
gdu /var                     # interactive TUI under /var
gdu -n /var                  # non-interactive, prints summary
gdu -np /var                 # no-progress, scripting-friendly
gdu -x /                     # stay on one filesystem
gdu -o report.json /var      # export scan as JSON
gdu -f report.json           # browse a saved scan
gdu --no-cross               # don't cross filesystem boundaries
gdu --ignore-dirs /proc,/sys /

Output: (none — exits 0 on success)

Inside the TUI: d delete, r rescan, n/s/C sort by name/size/items, t show modification time, q quit.

Comparison table#

ToolTypeLayerBest forNotes
dffilesystem summaryblockQuick “am I out of space?”POSIX-standard, on every system
dufile-tree walkpathFinding biggest dirs/filesSlow on huge trees
duffilesystem summaryblockColourful dashboard viewGo binary, JSON output, v0.9.1 (Sept 2025)
dustfile-tree walkpathOne-shot visual tree of largest dirsRust, parallel walk, gitignore-aware
gdufile-tree walker (TUI + CLI)pathFast interactive or scriptable analysisGo, fastest on NVMe, JSON export
ncdufile-tree walker (TUI)pathInteractive cleanupCurses UI, delete-in-place
lsof +L1open-file inspectorprocessDeleted-but-open file culpritsRequired when df > du

Filesystem cleanup recipes#

# 1. Find the top 20 biggest files anywhere on the root filesystem
sudo find / -xdev -type f -printf '%s\t%p\n' 2>/dev/null \
  | sort -rn | head -20 \
  | numfmt --to=iec-i --suffix=B --field=1

# 2. Find files older than 90 days under /var/log (candidates for deletion)
sudo find /var/log -xdev -type f -mtime +90 -printf '%TY-%Tm-%Td %s %p\n' \
  | sort | head -50

# 3. Find directories with the most files (inode hogs)
sudo find / -xdev -type d -print0 2>/dev/null \
  | xargs -0 -I{} sh -c 'echo "$(ls -A1 "{}" 2>/dev/null | wc -l) {}"' \
  | sort -rn | head -20

# 4. Show package-manager cache sizes
du -sh /var/cache/apt /var/cache/dnf /var/cache/yum ~/.cache 2>/dev/null

# 5. Show systemd journal disk usage
journalctl --disk-usage
sudo journalctl --vacuum-size=500M     # cap at 500 MB

# 6. Find Docker disk usage
docker system df
docker system prune -af                # remove all unused images/containers

# 7. Trim a partition without rebooting (ext4 with discard)
sudo fstrim -av

# 8. Empty trash and old backups
rm -rf ~/.local/share/Trash/files/*
find /backups -name '*.tar.gz' -mtime +30 -delete

# 9. Big files modified in the last day (recent culprits)
sudo find / -xdev -type f -size +100M -mtime -1 2>/dev/null

# 10. Cleanup pip / npm / cargo caches
pip cache purge
npm cache clean --force
cargo cache --autoclean   # via the cargo-cache crate

Output: (varies — exits 0 on success)

Monitoring disk over time#

For a quick continuous view, pair watch with df -h; for historical trending, log to a file and graph later. duf --json is useful inside Prometheus exporters or status scripts.

# Live refresh every 2 s
watch -n 2 'df -h | grep -v tmpfs'

# Log df every 5 minutes
while true; do
  echo "=== $(date -Iseconds) ===" >> /var/log/diskwatch.log
  df -h >> /var/log/diskwatch.log
  sleep 300
done &

# Alert when /var crosses 80%
df -h /var | awk 'NR==2 && $5+0 > 80 {print "WARN /var at "$5}'

Output: (none — exits 0 on success)

Edge cases and gotchas#

SymptomCauseFix
df shows full, du is much smallerDeleted files held by running processeslsof | grep deleted, restart process
”No space left” but df -h shows freeInode exhaustiondf -i, delete millions of small files
du slower than expectedCrossing into network/FUSE mountsAdd -x to stay on one filesystem
du and ls -l disagreeSparse files, hardlinks, or block alignmentdu --apparent-size for logical size
df shows 100% but writes workReserved blocks (5% for root by default)tune2fs -m 1 /dev/sdaN to lower reserve
du -sh /proc gives weird huge numbers/proc is a virtual filesystemAlways add -x or skip /proc and /sys
Permission denied warnings flood stderrWalking dirs the user can’t readAdd 2>/dev/null or run with sudo

[!TIP] When df -h shows a filesystem full but du -sh doesn’t account for all the space, look for deleted but open files: lsof | grep deleted | awk '{print $7, $NF}' | sort -rh | head -10. A running process may hold file descriptors to deleted log files, keeping the blocks allocated.

[!TIP] ext-family filesystems reserve 5% of blocks for root by default. On data-only volumes this is wasted space; reclaim it with sudo tune2fs -m 1 /dev/sdaN (1% reservation).

[!TIP] Always add -x to du and find when starting from / — otherwise the walk descends into /proc, /sys, network mounts, and bind mounts, inflating sizes and exposing permission-denied errors.

Sources#