du Command in Linux: Complete Guide to Disk Usage Estimation and Parameters

du command in Linux and it perimeters

I use du almost every week, usually in the exact same panicked context: a disk is filling up and I need to find out what’s actually eating the space, fast. It’s a deceptively simple command with a genuinely deep set of options once you get past du -sh. I want to walk through it properly — how it actually measures usage (which surprises people the first time), the full parameter set, and the workflows I lean on for real troubleshooting.

What du Does

du (disk usage) walks a directory tree and reports how much disk space each file and directory actually consumes on disk, recursively summing up to whatever level you ask for. This is a fundamentally different measurement from a file’s logical size — du reports the number of disk blocks allocated, not the byte count you’d see from ls -l.

Syntax

du [OPTION]... [FILE]...
du [OPTION]... --files0-from=F

Core Options

I tested all of these directly to confirm real output formatting:

OptionLong formDescription
-a--allShow sizes for files too, not just directories
-h--human-readableSizes in K/M/G/T instead of raw block counts
-s--summarizeOnly show a total for each argument, not every subdirectory
-c--totalPrint a grand total after processing all arguments
--max-depth=NLimit recursion depth in the summary output
-x--one-file-systemDon’t cross into other mounted filesystems while recursing
--exclude=PATTERNSkip files/directories matching a glob pattern
-B SIZE--block-size=SIZEReport sizes in fixed units (e.g., -B M for megabytes)
-L--dereferenceFollow symlinks rather than counting the link itself
--apparent-sizePrint logical file size (like ls) instead of actual disk block allocation
--timeShow the last modification time of each file/directory alongside size
-0--nullEnd each output line with NUL instead of newline, safe for scripting on odd filenames

Tested Examples

I built a small directory with known file sizes to demonstrate exactly how output scales:

$ mkdir -p duf_test/sub1 duf_test/sub2
$ dd if=/dev/zero of=duf_test/file1.bin bs=1M count=5      # 5 MB
$ dd if=/dev/zero of=duf_test/sub1/file2.bin bs=1M count=3 # 3 MB
$ dd if=/dev/zero of=duf_test/sub2/file3.bin bs=1K count=200 # 200 KB

Default output (1K blocks)

$ du duf_test
204     duf_test/sub2
3076    duf_test/sub1
8404    duf_test

Human-readable

$ du -h duf_test
204K    duf_test/sub2
3.1M    duf_test/sub1
8.3M    duf_test

Summarize only (single total)

$ du -sh duf_test
8.3M    duf_test

This is the single most common invocation I run — du -sh <path> — to instantly answer “how big is this directory, total.”

Show every file, not just directories

$ du -ah duf_test
200K    duf_test/sub2/file3.bin
204K    duf_test/sub2
3.0M    duf_test/sub1/file2.bin
3.1M    duf_test/sub1
5.0M    duf_test/file1.bin
8.3M    duf_test

Limiting recursion depth

$ du -h --max-depth=1 duf_test
204K    duf_test/sub2
3.1M    duf_test/sub1
8.3M    duf_test

This is invaluable on deep directory trees (like a node_modules folder or a large web root) where full recursive output would be thousands of lines — --max-depth=1 gives you exactly the top-level breakdown you actually want before drilling further.

Grand total across multiple arguments

$ du -ch duf_test/sub1 duf_test/sub2
3.1M    duf_test/sub1
204K    duf_test/sub2
3.3M    total

Why du’s Numbers Don’t Match ls -l

This trips up almost everyone the first time. ls -l reports a file’s apparent size — the exact logical byte count of its content. du reports actual disk block allocation, which differs for two main reasons:

  1. Block rounding — filesystems allocate space in fixed block units (commonly 4 KB on ext4). A 1-byte file still consumes a full block on disk, so du reports 4K even though ls -l reports 1.
  2. Sparse files — a file can have a huge logical size but contain large unwritten “holes” that consume no actual disk blocks. A 10 GB sparse file (common for VM disk images) might report 10G from ls -l but only a few hundred MB from du, because du only counts blocks actually allocated on disk.

If you specifically want du to match ls -l logical sizes instead of block allocation, use --apparent-size:

du --apparent-size -h duf_test/file1.bin

How du Works Internally

du performs a recursive directory walk (functionally similar to what find does), calling stat() (technically lstat() by default, so it doesn’t follow symlinks unless -L is given) on every entry. The st_blocks field returned by stat() — the number of 512-byte blocks actually allocated to the file by the filesystem — is what du sums, then converts to whatever display unit you requested. This is why du can be genuinely slow on directories with millions of small files: it’s not reading file content, but it is issuing a system call per entry, and that per-call overhead dominates on huge trees, especially over network filesystems like NFS where each stat() is a round trip.

This also explains hard links: a file with multiple hard links pointing to the same inode is only counted once in du‘s totals by default (unless you use -l/--count-links to force counting each link separately), because du recognizes it’s the same underlying disk allocation.

Real-World Troubleshooting Workflow

When a disk fills up unexpectedly, this is the sequence I run, almost every time:

# 1. Confirm which filesystem is actually full
df -h

# 2. Find the largest top-level directories under root
du -h --max-depth=1 / 2>/dev/null | sort -rh | head -20

# 3. Drill into whichever directory looks disproportionate
du -h --max-depth=1 /var/log 2>/dev/null | sort -rh | head -10

# 4. Once you're close to the culprit, list individual files too
du -ah /var/log/myapp 2>/dev/null | sort -rh | head -20

I always pipe through sort -rh (reverse, human-numeric sort) — without it, du -h output is in directory-traversal order, not size order, and eyeballing which line is biggest across dozens of entries is slow and error-prone.

Finding the Top 10 Largest Files System-Wide

find / -xdev -type f -exec du -h {} + 2>/dev/null | sort -rh | head -10

I use find ... -exec du here rather than a single recursive du -ah /, because find lets me filter by type and stay on one filesystem (-xdev) cleanly, and this pattern scales better on very large trees since it avoids du building a full in-memory recursive summary before output.

Automation Example: Disk Usage Alerting Script

#!/bin/bash
set -euo pipefail
THRESHOLD_MB=1024
TARGET_DIR="/var/log"

while IFS=$'\t' read -r size path; do
  size_mb=$(numfmt --from=iec "${size}" 2>/dev/null | awk '{print int($1/1048576)}')
  if [ "$size_mb" -ge "$THRESHOLD_MB" ]; then
    echo "ALERT: $path is using ${size} (over ${THRESHOLD_MB}MB threshold)"
  fi
done < <(du -h --max-depth=2 "$TARGET_DIR" 2>/dev/null)

This kind of script is the backbone of many simple monitoring checks I’ve deployed before reaching for a full metrics stack — it’s cheap, dependency-free, and easy to run from cron.

Performance Optimization

  • Avoid du -a on huge trees unless you truly need per-file granularity; summarized (-s) or depth-limited output is dramatically faster since it still walks every file but avoids formatting and printing every single line.
  • Use -x/--one-file-system when scanning / to avoid recursing into mounted network shares or bind mounts, which can be extremely slow or even hang on stale NFS mounts.
  • Prefer ncdu (not part of core du, but built on the same underlying stat-walk concept) for interactive exploration — it caches the walk once and lets you browse results without re-scanning, which is much faster for repeated investigation of the same tree.
  • On very large filesystems, consider filesystem-native usage reporting instead: btrfs filesystem du, or ZFS’s zfs list -o space, which can read pre-computed accounting data rather than performing a live walk.

du vs Related Commands

CommandPurpose
duActual disk space consumed by files/directories, walked recursively
dfFree/used space at the filesystem (mount point) level, from the kernel’s live block accounting, not a directory walk
ls -lLogical/apparent file size, not actual disk allocation
statDetailed metadata for a single file, including both apparent size and st_blocks
ncduInteractive, cached, browsable version of du‘s output

A distinction worth internalizing: du and df can legitimately disagree. df reflects the filesystem’s live block accounting (including space held by deleted-but-still-open files, filesystem reserved blocks, and journal overhead), while du only reflects what it can see by walking the visible directory tree. A classic cause of “df says full but du can’t find the files” is a large file that’s been deleted but is still held open by a running process — the space isn’t freed until that file descriptor closes, and du can’t see a deleted file at all, but df still counts the space as used. lsof +L1 or lsof | grep deleted is the standard way to hunt that down.

Security Implications

du requires read and execute permission on directories to traverse them; running it as an unprivileged user against directories you don’t own will silently skip content you can’t read and may under-report actual usage without necessarily erroring loudly (depending on the directory’s permission structure), which can be misleading during a “what’s using my disk” investigation — running as root gives a complete picture when needed. Also worth knowing: because du performs a full filesystem tree walk, running it against attacker-controlled paths (deeply nested or maliciously crafted directory structures) can be used as a low-effort denial-of-service vector against automated scripts that shell out to du on user-supplied paths without limits — apply --max-depth, timeouts, or path validation in any automation that runs du against untrusted input.

Distribution Compatibility

du is part of GNU coreutils and ships identically across Debian, Ubuntu, Fedora, RHEL/CentOS, Arch, and openSUSE. BusyBox-based systems (Alpine, embedded distros) include a lighter du implementation with a smaller flag set — notably, --max-depth and --apparent-size are supported in BusyBox’s version, but some GNU-specific long options may be missing, so scripts intended to be portable to Alpine containers should stick to the short-flag core options (-s, -h, -a, -c).

du and Compression-Aware Filesystems

On filesystems that support transparent compression — Btrfs with compress=zstd, or ZFS with compression=lz4 — du‘s default behavior (reporting st_blocks, actual allocated blocks) reflects the compressed on-disk size, not the logical uncompressed size of the file’s content. This can produce genuinely surprising results: a 100 MB log file full of repetitive text might report as 8 MB under du on a compressed Btrfs subvolume, while du --apparent-size on the same file would report the full 100 MB logical size. Neither number is “wrong” — they’re answering different questions (actual disk consumption versus logical content size), but conflating the two is a common source of confused capacity planning on compressed filesystems. When auditing disk usage on Btrfs or ZFS specifically, I lean on the filesystem’s own native usage-reporting tools (btrfs filesystem du, zfs list -o space) alongside plain du, since they’re aware of shared blocks between snapshots in a way du‘s simple per-file walk fundamentally can’t be.

Excluding Paths During a Scan

For large trees where certain subdirectories are irrelevant to the investigation (build artifacts, .git history, cache directories), --exclude keeps output focused and meaningfully speeds up the scan:

du -sh --exclude='.git' --exclude='node_modules' /home/dev/projects/*

I use this constantly on development machines specifically to answer “how much space are my actual project files using” without the noise of regenerable build output and dependency trees, which frequently dwarf the source code itself in raw byte count.

Combining du with find for Age-Aware Cleanup

A pattern I rely on for cleaning up old build artifacts or log rotation leftovers combines find‘s time filtering with du‘s size reporting:

find /var/log -name "*.log.gz" -mtime +30 -exec du -h {} \; | sort -rh | head -20

This surfaces the largest old compressed logs specifically, rather than just the largest logs overall, which is usually the more actionable list when the goal is reclaiming space from things safe to delete.

Summary

du is one of those commands whose basic form (du -sh) you’ll use constantly, but whose deeper flags — --max-depth, --apparent-size, -x, -c — are what actually make it a serious troubleshooting tool rather than a one-liner. Understanding that it measures real block allocation rather than logical size, and that it can diverge from df, will save you real confusion the first time a “full” disk doesn’t match what du reports.

References

  • GNU Coreutils Manual — du invocation: https://www.gnu.org/software/coreutils/manual/html_node/du-invocation.html
  • Linux man-pages — du(1): https://man7.org/linux/man-pages/man1/du.1.html
  • Linux man-pages — stat(2): https://man7.org/linux/man-pages/man2/stat.2.html
  • Red Hat Documentation — Monitoring Disk Usage: https://access.redhat.com/documentation/
Total
0
Shares

Leave a Reply

Previous Post
df command in Linux and it perimeters

df Command in Linux: Complete Guide to Disk Space Reporting and Parameters

Next Post
fdformat command in Linux and it perimeters

fdformat Command in Linux: Complete Guide to Formatting Floppy Disks and Parameters

Related Posts