Disk full.
Two words that have ruined more of my Friday afternoons than any bug I've ever shipped. Early in my career, I crashed a server in the middle of a deployment because nobody was watching directory sizes. Not a race condition. Not a memory leak. Just a log directory that grew quietly for months until there was nowhere left to write.
Since then, checking directory size has been a reflex. Here are the commands I actually use, the gotchas that bite everyone eventually, and the one discrepancy between du and df that will confuse you at the worst possible moment.
The Short Version
du -sh /path/to/directory # total size of one directory
du -h --max-depth=1 /path | sort -hr # which subdirectories are eating the space
ncdu /path # interactive explorer (install it first)
df -h . # is the filesystem itself full?
| I want to... | Command |
|---|---|
| Total size of a directory | du -sh /path |
| Each subdirectory, biggest first | du -h --max-depth=1 /path | sort -hr |
| Explore (and delete) interactively | ncdu /path |
| Find the biggest files, not directories | find /path -type f -exec du -h {} + | sort -hr | head -20 |
| Check overall disk space | df -h |
| Analyze a remote server | ssh host "ncdu -o- /path" | ncdu -f- |
That's 90% of the job. The rest of this post is the other 10%: the flags, the gotchas, and the tools worth installing.
du: The Command That Answers the Question
du (disk usage) walks a directory tree and sums up the space everything takes. The two flags you'll type every time:
$ du -sh /var/log
1.2G /var/log
-s— summary. Just the grand total, not a line per file.-h— human-readable. Sizes in KB, MB, GB instead of raw block counts.
Without -s, du prints every file and subdirectory it walks. On a big tree, that's a firehose.
Run it with sudo on system directories. du can only count what it can read. Run it on /var or /etc without sudo and you'll get a spray of "Permission denied" errors — and a total that's quietly, dangerously wrong.
A few more flags worth knowing:
-c— add a grand-total line when you pass several paths:du -shc /var/log /var/lib-x— stay on one filesystem, so you don't wander into/proc,/sys, or mounted volumes--exclude='node_modules'— skip anything matching a pattern--apparent-size— report actual file sizes instead of allocated blocks (matters for sparse files)-L— follow symlinks, whichduskips by default (one reason totals sometimes look smaller than expected)
Which Subdirectory Is Eating the Space?
You'll see this recipe in a lot of tutorials:
du -h /var | sort -hr # technically works, practically a mess
It sorts, sure. But it also lists every nested directory in the entire tree — hundreds of lines where the noise buries the signal. What you almost always want is one level at a time:
$ sudo du -h --max-depth=1 /var | sort -hr
9.4G /var
4.1G /var/lib
2.8G /var/log
612M /var/cache
Now the culprit is obvious, and you can drill into /var/lib and repeat. On macOS, the flag is -d1 instead of --max-depth=1 (BSD du spells it differently).
Two gotchas that bite everyone:
Hidden directories. Shell globs skip dotfiles, so du -sh * never sees .cache, .npm, or the .git directory quietly holding 8 GB of history. Include them explicitly:
du -sh .[!.]* * 2>/dev/null | sort -hr
Same hunt, but for files instead of directories:
find /var -type f -exec du -h {} + | sort -hr | head -20
ncdu: The One Tool Worth Installing
As much as I love the terminal, piping du into sort gets old when you're actually cleaning house. ncdu (NCurses Disk Usage) is an interactive du, and it's what I reach for on any box I'm spending more than five minutes on:
sudo apt-get install ncdu # Debian/Ubuntu
sudo dnf install ncdu # Fedora/RHEL
brew install ncdu # macOS
Then just run ncdu /var. Arrow keys to navigate, Enter to drill in, d to delete the highlighted file or directory (it asks first), r to rescan. It turns "find the bloat" from a dozen commands into thirty seconds of arrow keys.
The killer trick: analyze a remote server from your local terminal.
ssh production "ncdu -o- /var" | ncdu -f-
That streams the scan from the remote box (where ncdu needs to be installed) into your local ncdu, so you browse a server's disk like it's your own.
If You Like Shiny Things: dust, gdu, and duf
du has been around since the early '70s, and it shows. The modern reimplementations are worth a look:
- dust —
durewritten in Rust. Fast, and prints a tree with bars and percentages so the big stuff jumps out:dust /var - gdu — written in Go, parallelized for SSDs, with an ncdu-style interface. Noticeably quicker on huge trees.
- duf — a
dfreplacement with output designed for humans who like color.
None of them replace du — it's already installed everywhere, which on a stranger's production server is the only feature that matters. But on your own machines, they're a quality-of-life upgrade.
Why ls Doesn't Answer the Question
A question I get a lot: "Can't I just use ls -lh?" Try it and you'll notice something odd — every directory reports almost exactly the same size:
$ ls -lh
drwxr-xr-x 14 matt staff 4.0K Jul 19 09:14 projects
projects could hold 40 GB of code and ls would still say 4.0K. That's because ls shows the size of the directory's own entry — the filesystem bookkeeping that lists which files live in it — not the contents. A directory is really just a list of names pointing at inodes; its "size" is the size of that list. Getting the size of everything it points at requires walking the whole tree, which is exactly what du does.
du vs df: When the Numbers Don't Add Up
df is the other half of this story. It reports space for entire filesystems:
$ df -h .
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 100G 91G 4.2G 96% /
df asks the filesystem for its summary totals — instant. du walks the tree and adds things up — slower, but precise about where the space went. Usually they roughly agree. When they don't, it's one of three things:
- Deleted files that are still open. A process holding a deleted file keeps its blocks allocated.
dfcounts them;ducan't see them, because the file no longer has a name. Classic case: the 20 GB log you deleted while the app still had it open. The space comes back only when the process lets go. Find the culprits withsudo lsof +L1, then restart the offending process. - Data hiding under a mount point. If someone wrote files to
/databefore a volume was mounted there, those files are shadowed —du /walks right past them whiledfcounts them. Bind-mount the root filesystem somewhere temporary to see what's underneath. - Reserved blocks. ext4 reserves about 5% of the filesystem for root by default.
dfaccounts for it;dunever will.
Learn this before 2 a.m. on the night a disk reads 100% full and du swears there's nothing there. It is not a fun puzzle to solve for the first time under pressure.
Scripting It, So the Server Warns You First
A while back I was consulting for a company whose CI/CD pipeline kept failing. The culprit wasn't the code — it was unmonitored log directories slowly eating the build server's disk. The fix wasn't a hero cleanup session; it was a tiny script in cron:
#!/bin/bash
# Yell about any log directory that has grown past 5 GB
limit=$((5 * 1024 * 1024)) # in KB
du -sk /var/log/*/ | while read -r kb dir; do
if [ "$kb" -gt "$limit" ]; then
echo "WARNING: $dir is now $((kb / 1024 / 1024)) GB"
# wire this into Slack, PagerDuty, or email — and archive old logs:
# find "$dir" -name '*.log' -mtime +30 -exec gzip {} \;
fi
done
Two details matter here. First, use -sk (kilobytes) for comparisons — parsing "1.2G" strings in a script is fragile, and -sk is POSIX, so it behaves the same on every Linux and macOS. Second, pair it with a df-based check that warns when any filesystem crosses 85% full. Run both from cron daily, and "disk full" stops being a surprise that picks the worst possible moment.
FAQ
How do I find the largest directories on a Linux server?
Start at the root and work down one level at a time: sudo du -hx --max-depth=1 / | sort -hr, then repeat inside whatever wins. The -x keeps you on one filesystem so you don't wander into /proc or mounted volumes. If you can install one tool, ncdu / makes the same hunt interactive.
Why does df show more used space than du?
Almost always one of three things: deleted files still held open by a process (check sudo lsof +L1), data hidden underneath a mount point, or the filesystem's reserved blocks. The section above walks through each.
Why does ls show 4.0K for every directory?
That's the size of the directory's own entry — the list of filenames it contains — not the size of its contents. Only du (or something built on the same idea) walks the tree and totals the contents.
How do I include hidden files and folders?
Shell globs skip dotfiles. From inside the directory, use du -sh .[!.]* * 2>/dev/null | sort -hr. ncdu shows hidden entries by default, which is another reason to reach for it.
Does this work on macOS?
Mostly. du and df ship with macOS, but use -d1 instead of --max-depth=1, and the GNU-only flags like --exclude and --apparent-size aren't there. brew install ncdu dust duf gets you the extras.
What's the fastest way to scan a huge directory?
du stats every file, so millions of small files (node_modules, CI workspaces) take a while. dust and gdu parallelize the work. And ncdu can save a scan (ncdu -o scan.json /var) and reload it later (ncdu -f scan.json) without walking the tree again.
Wrapping Up
The commands are trivial. The habit is what saves you.
du -sh for the quick answer. --max-depth=1 | sort -hr for the hunt. ncdu when it's cleanup time. df when the numbers don't add up. And if you take one thing from this post, take the script: put the check in cron and let the server watch itself, because disks only fill up at the worst possible time.





