macOS Disk Reporting

On Linux with ext4, XFS, or ZFS, df -h and du -sh are reliable. On macOS with APFS, both tools give misleading results for fundamentally different reasons.

Why df lies

Reason 1: purgeable space inflates “Available”

The macOS kernel’s APFS statvfs() implementation adds purgeable blocks to f_bavail. So the “Available” column in df -h includes space that is currently occupied by purgeable data (local TM snapshots, iCloud evictable files, cached content). This space is not immediately writable. It will only be freed if the OS decides it needs to under storage pressure.

You might see df -h show 200 GB available while you are actually unable to write a 100 GB file without triggering a cascade of cache evictions.

Finder and the “About This Mac” storage bar use a different API (NSURLVolumeAvailableCapacityForImportantUsageKey) that excludes purgeable space from the “available” figure. This is why Finder and df can show radically different available space numbers.

Reason 2: the “Total” column reflects the container, not the volume

Every APFS volume reports its “total” capacity as the container’s total capacity. If you have a 1 TB container with a System volume (15 GB consumed), a Data volume (600 GB consumed), a VM volume (32 GB consumed), and 353 GB free, every volume you run df -h on reports ~1 TB total and ~353 GB available. This is technically accurate but deeply misleading — it doesn’t tell you how much space each volume has consumed.

What df output actually means on macOS

df columnWhat it actually is
SizeAPFS container total capacity
UsedThis volume’s consumed blocks (live files only, not snapshots)
AvailContainer free space plus purgeable space on this volume
Capacity%Used / Size — misleading for the reasons above

Why du lies

du works by walking a directory tree via getdents()/readdir() and summing st_blocks (the number of 512-byte blocks allocated to a file, as reported by stat(2)) for every inode it encounters. It is a directory-tree accounting tool, not a block-layer accounting tool. On APFS, it has three failure modes.

du will not descend into firmlinked directories when run from /System/Volumes/Data/. Running sudo du -hd1 /System/Volumes/Data/ returns almost nothing — not because of permissions, but because firmlinks are bidirectional and du treats them as boundaries.

Fix: enumerate each top-level directory individually:

sudo du -sh /System/Volumes/Data/Users/ \
           /System/Volumes/Data/Library/ \
           /System/Volumes/Data/private/ \
           /System/Volumes/Data/Applications/ 2>/dev/null

See APFS - Firmlinks and the Split Volume Design for the full list of directories to check.

Failure mode 2: clone overcounting

When files share APFS extents (clones), du counts the blocks once per directory entry. A 10 GB file cloned 5 times reports as 50 GB to du, even though actual storage consumption is 10 GB (plus metadata). macOS uses cloning heavily internally.

Failure mode 3: snapshot blindness

Blocks held by APFS snapshots (the “stranded” old versions of files you’ve modified or deleted since the snapshot was taken) have no live directory entry. du cannot see them at all. A snapshot from 6 months ago on an active developer machine might be retaining 100+ GB of stranded blocks that are completely invisible to du.

Failure mode 4: deleted-but-open files are completely invisible

Deleted-but-open files — files that have been unlinked but are still held open by a running process — have no directory entry at all. du cannot see them, find cannot see them, Finder cannot see them, DaisyDisk cannot see them. Only lsof +L1 can detect them by querying the kernel’s file descriptor tables.

This is the most common cause of large invisible disk usage. In one real case, 708 GB was consumed by a log shipping agent holding deleted log files open.

What to trust

ToolWhat it sees
du (per-directory)Live files with directory entries. Must enumerate directories individually due to firmlinks.
diskutil apfs listTotal volume consumption including snapshots, metadata, and deleted-open files.
lsof +L1Deleted-but-open files — the gap that neither du nor DaisyDisk can explain.
diskutil apfs listSnapshotsSnapshot-retained blocks.

The gap between du totals and diskutil Capacity Consumed = deleted-open files + snapshot-retained blocks + APFS metadata.

df and du are useful for quick sanity checks but should never be your authoritative source. Use diskutil and lsof +L1 instead.

Commands that do NOT work on macOS

CommandWhy it fails
du -hd1 /System/Volumes/Data/Firmlinks block traversal at root
du -hxd2 /System/Volumes/Data/Same — -x doesn’t help with firmlinks
df -h /Shows container-level info, not per-volume breakdown
diskutil info / → “Volume Purgeable Space”Field doesn’t exist on all macOS versions/volume types
DaisyDisk / Finder “About This Mac”Cannot see deleted-open files (no directory entries)

See also