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 column | What it actually is |
|---|---|
| Size | APFS container total capacity |
| Used | This volume’s consumed blocks (live files only, not snapshots) |
| Avail | Container 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.
Failure mode 1: firmlinks block traversal at the Data volume root
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/nullSee 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
| Tool | What it sees |
|---|---|
du (per-directory) | Live files with directory entries. Must enumerate directories individually due to firmlinks. |
diskutil apfs list | Total volume consumption including snapshots, metadata, and deleted-open files. |
lsof +L1 | Deleted-but-open files — the gap that neither du nor DaisyDisk can explain. |
diskutil apfs listSnapshots | Snapshot-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
| Command | Why 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
- APFS - Container and Volume Model — why
df“Total” is the container’s capacity - APFS - Purgeable Space — why
df“Available” is inflated - APFS - Copy-on-Write and Clones — why
duovercounts cloned files - APFS - Snapshots — why
duundercounts by missing snapshot-retained blocks - APFS - Firmlinks and the Split Volume Design — why
dufails at the Data volume root - Deleted-But-Open Files — the most common cause of large invisible usage
- diskutil - The Ground Truth Tool — ground truth for APFS accounting