df Command: Tutorial & Examples

One line per mounted filesystem, one glance at every drive in the box.

What It Is

df — short for disk free — is the command you type when something on the server feels stuck and you suspect the drive is full. One short word, one table, one number per filesystem that tells you whether you have room to breathe. It's been on every UNIX since 1971, it needs no setup, and like top it only looks — it never writes a thing.

If you've never run a server, this is the second command you should learn after top. Disks fill up. Logs grow, package caches bloat, a runaway process spews 40 GB of stack traces into /var/log, and suddenly nothing works — your database refuses writes, your web server can't open a socket file, ssh won't even let you log in to fix it. df is how you find out, in two seconds, which filesystem ran out and how badly. We'll explain every column, teach you to read it the way an admin does, and along the way pick up how Linux storage actually works — partitions, mountpoints, inodes, block devices, and the genuinely-surprising second way a disk fills up that catches everyone at least once.

Your First Look

Always pass -h (human-readable) — nobody reads raw 1K blocks:

df -h
Filesystem                Size  Used Avail Use% Mounted on
udev                       16G     0   16G   0% /dev
tmpfs                     3.1G  2.1M  3.1G   1% /run
/dev/mapper/xps--vg-root  1.8T  1.4T  322G  82% /
tmpfs                      16G  792M   15G   5% /dev/shm
tmpfs                     5.0M  8.0K  5.0M   1% /run/lock
tmpfs                      16G  330M   16G   3% /tmp
/dev/nvme0n1p2            944M  354M  525M  41% /boot
/dev/nvme0n1p1            975M   23M  952M   3% /boot/efi
tmpfs                     3.1G  216K  3.1G   1% /run/user/1000

One row per mounted filesystem, six columns: where the bytes live, how big it is, how much is used, how much is free, the percentage, and the directory you reach it through. Sorted by mount order, which is roughly boot order — / first, then everything mounted on top of it. The single number your eye is hunting for is Use% on the rows that aren't tmpfs. On this laptop it's the root filesystem at 82% — getting tight, not yet on fire.

How I Read It

Three seconds, in this exact order — the muscle memory of every admin who's been paged for disk-full at 3am.

First, I ignore everything labelled tmpfs, devtmpfs, or efivarfs. Those aren't real disks — they're RAM-backed filesystems the kernel creates for /dev, /run, /tmp, shared memory and per-user runtime directories. Useful, but they can't "fill up" in the way you're worried about — if /dev/shm hits 100% it means you're out of RAM, not disk, and that's a different problem (out-of-memory, not disk-full). My eye skips straight past every tmpfs row. What's left — /dev/mapper/..., /dev/nvme0n1p2, /dev/sda1 — those are the real ones.

Second, I scan the Use% column for any row over 80%. That's the warning line. 90% is the trouble line. 100% means something is already broken on that mountpoint, even if nothing's complained yet. The Mounted on column tells me what's affected: if / is full, everything is in danger; if /boot is full (it's small, 1 GB on most distros), the next kernel upgrade will fail silently; if /var is on its own partition and it's full, logs, databases and package caches will choke first.

Third — and this is the move that took me years — if Use% looks fine but something is screaming "No space left on device," I run df -i. That's the inodes view, and it's the secret second way a disk fills up. We get to it under Gotchas; it's important enough to live there in red.

The other reflex worth knowing: df tells me the filesystem is full. To find out which directory is hoarding the bytes, I switch to dudf asks the filesystem ("how much have I handed out?"), du walks the files ("how big are the files actually on disk?"). They sometimes disagree, occasionally wildly, and the gap is its own story (see Gotchas).

Warning

if df -h shows plenty of free space but programs are failing with "No space left on device," run df -i. Every file on ext4 needs an inode, and the inode pool is fixed when the filesystem is created. A directory holding millions of tiny files — mail spools, session caches, Docker overlay layers, an unbounded cache directory — can exhaust inodes while the disk itself is half empty. The symptom is identical to a full disk; the cure is to delete files (not free up bytes — free up entries).

The Columns Explained

Every column in df -h, in order:

  • Filesystem — the source, what's actually providing the storage. For a normal disk partition this is a block device path like /dev/sda1 or /dev/nvme0n1p2 (the second partition of the first NVMe drive). For LVM it's a logical-volume path like /dev/mapper/xps--vg-root. For NFS it's server:/export. For pseudo-filesystems it's a name the kernel made up — tmpfs, udev, efivarfs. What's listed here is not always a disk — that's important.
  • Size — the total capacity of this filesystem, after filesystem overhead. Always smaller than the partition it sits on (a few percent goes to metadata, journals, and reserved blocks).
  • Used — bytes currently allocated to files. Includes filesystem overhead, so two files that look like 100 MB on the disk may add up to 105 MB used.
  • Avail — bytes you can still write as a regular user. This is the one that matters. On ext4 it's typically Size − Used − 5% reserved. That 5% is held back for root so the system can keep limping even when users have filled the disk — which is why Used + Avail doesn't equal Size. You can shrink the reservation with tune2fs -m if you need the space, but it's a foot-gun on /.
  • Use%Used / (Used + Avail) × 100, rounded up. So a filesystem can read 100% while still having a few hundred MB available (the root reserve); regular users see "no space," root can still write.
  • Mounted on — the directory the filesystem is grafted into the tree at. The path you'd cd to to reach those bytes. This is the field that tells you what's affected: a full /var means logs and databases; a full /boot means the next kernel upgrade fails; a full / means everything.

Add -T for a Type column (ext4, xfs, btrfs, vfat, tmpfs, nfs). Add -i to replace size/used/avail with inode counts. Add --output= with a comma-separated field list to build your own table:

df -h --output=source,fstype,size,used,avail,pcent,target

Valid fields: source, fstype, itotal, iused, iavail, ipcent, size, used, avail, pcent, file, target.

Reading It by Example

A handful of patterns to build instinct.

/dev/sda1 ... 82% / on the only real row — getting full, not yet broken. Time to figure out what is growing. Next command is du -sh /* 2>/dev/null | sort -h | tail to see which top-level directory is the culprit, then drill in.

/dev/sda1 ... 100% /, Avail 0 — you're out, the system is in trouble now. Daemons that try to write logs are failing, apt refuses to install, MySQL may have crashed. Triage: clear the package cache (apt clean), rotate big files in /var/log, check the journal (journalctl --vacuum-size=200M). Buy yourself room first, find the cause second.

df -h shows 60% /, but programs report "No space left on device" — inodes. Run df -i; you'll see IUse% 100% somewhere. Look for directories with millions of files: find / -xdev -type f 2>/dev/null | cut -d/ -f1-3 | sort | uniq -c | sort -n | tail. Common culprits: an unbounded session/cache dir, mail queues in /var/spool/postfix, Docker overlay layers under /var/lib/docker.

/boot ... 95% /boot — three old kernels are still installed. The next apt upgrade will fail mid-install. Clean with apt autoremove (Debian/Ubuntu) or dnf remove --oldinstallonly (Fedora/RHEL).

A tmpfs row at 100% — not a disk problem. You ran out of RAM backing that tmpfs. Could be /tmp (mounted as tmpfs on many distros), /dev/shm (a process writing huge files to shared memory), or a container's overlay. Fix is upstream — kill the writer, or back the mount with real disk in /etc/fstab.

A row reading df hangs forever — a stuck remote mount, almost always NFS. The kernel is waiting for a server that isn't answering. Same family as the load-100, idle-CPU story under top. Cure: df -l (local filesystems only) or df --exclude-type=nfs to skip the wedged ones.

/dev/mapper/...-root 1.8T but you only have a 1 TB disk — that's LVM striping or RAID presenting multiple physical drives as one filesystem. The "disk" in df is whatever the kernel calls a filesystem, not necessarily a single physical drive. Cross-check with lsblk to see the hardware tree.

A /boot/efi row at 66% on a tiny 975 M vfat partition — totally normal. The EFI System Partition holds the bootloader binaries; it shouldn't grow under normal use, only when you install a new kernel or a new distro. If it is growing, look for leftover .efi files from old kernels.

Two rows for the same /dev/sda1 — you bind-mounted or remounted it somewhere. Both mountpoints report identical usage because they're the same filesystem; that's not double-counting, it's two doors into one room.

Cheat Sheet

The invocations worth memorizing:

  • df -h — human-readable sizes (G, M, K). The default starting point.
  • df -hT — add the filesystem type column. Tells you ext4 vs xfs vs tmpfs at a glance.
  • df -iinodes instead of bytes. Run this whenever "no space" doesn't match what df -h says.
  • df -h /var/log — show the filesystem containing a path. Useful when you don't know which mount a directory lives under.
  • df -h --total — add a grand-total row at the bottom.
  • df -h -x tmpfs -x devtmpfs — hide the noisy pseudo-filesystems; show only what can really fill up.
  • df -l — local only (skip NFS, CIFS). Use this if df hangs.
  • df -h --output=source,fstype,size,used,avail,pcent,target — pick your own columns.
  • df -H — powers of 1000 (marketing GB), not 1024. Almost never what you want.

Pro Tip

if you ever want both views in one shot — bytes and inodes side by side — use df --output=source,size,used,avail,pcent,itotal,iused,ipcent,target. One table, both ways a filesystem fills up, no second invocation.

How You'll Actually Use It

In real life, df lives in three places. First, the 3am reflex: page about a service down, ssh in, type df -h, eyes go straight to Use% on the real filesystems. Second, the monitoring script — every cron-based "alert me if disk hits 90%" check on earth is built on df, parsed with awk:

df -P --output=pcent,target | tail -n +2 | awk '{ gsub("%","",$1); if ($1+0 > 90) print }'

Third, the planning glance: before adding a service, before turning on debug logging, before pulling a Docker image, you check df -h to see what room you actually have.

What df is not for: finding which directory ate the space. That's du. And if df says full but du says half-empty, that's the deleted-but-still-open trick — a process is writing to a log file you've already rm'd, and the bytes don't get released until the process closes the file. lsof +L1 lists exactly those zombie files; restarting the offender (or truncating the FD via /proc/<pid>/fd) is the cure. That dfdu discrepancy is one of the small joys of learning Linux storage.

Gotchas

  • tmpfs is not a disk. Every tmpfs row in df is RAM. A full tmpfs means out of memory, not out of disk.
  • Used + Avail < Size. That gap is the 5% root-reserved block on ext4 (and similar on xfs). Regular users hit "no space" before the partition is truly full.
  • df -h shows the filesystem, not the disk. One physical drive can hold many partitions, each its own filesystem row; one filesystem can span many drives (LVM, RAID, ZFS, Btrfs). The "disk" you asked about isn't always a disk. For the actual hardware view, use lsblk.
  • df and du disagree. Three usual reasons: (1) a process has a deleted file still open (use lsof +L1 to find it); (2) something is mounted over a directory that already had files, hiding them from du; (3) sparse files or hardlinks confuse one side or the other. df is the authority on what the filesystem thinks; du is the authority on the visible files. The gap is the diagnosis.
  • df can hang on a dead NFS mount. The kernel is waiting on a server that isn't answering. Use df -l or df --exclude-type=nfs; longer-term, mount with soft,intr or move to autofs.
  • A 100% inode filesystem looks exactly like a 100% byte filesystem to applications. Always check both. df -i.
  • You can't grow inodes after the fact on ext4. The count is baked in at mkfs time (-N or -i bytes-per-inode). The cure for an inode-full ext4 is to delete files, or to back up, reformat with more inodes, and restore. XFS and Btrfs allocate inodes dynamically — no such limit.
  • Use% rounds up. A filesystem showing 100% may still have a kilobyte free. A filesystem showing 0% may already have tens of MB written. Trust the bytes, not the percent, in the corner cases.

History & Philosophy

df shipped in the very first edition of UNIX in 1971 — the same vintage as ps and ls. Its job hasn't changed in fifty-five years: ask the filesystem for the block-and-inode counters it already keeps in its superblock, and print them. The kernel does the accounting on every write; df just reads it out. It's almost free to run — no walking the directory tree, no stat-ing files, just a statvfs(2) call per mount.

And the secret that makes the whole storage world click: df is just a thin face over /proc. Every mounted filesystem appears in /proc/mountscat that file and you'll see the same list df showed you, plus mount options. The size and free-block counters come from statvfs on each mountpoint, which the kernel computes from the filesystem's superblock. No magic — just a POSIX call wrapped in a 1971-era table. The same "everything is a file" idea that powers top and ps: a running Linux box describes itself in plain text under /proc, and the classic tools are tiny formatters on top.

That's why df is on every UNIX, every Linux, every BSD, every macOS, identical enough that a script written in 1985 still runs today. The kernel changed everything underneath — ext2 became ext4, then XFS, Btrfs, ZFS, overlay — and the six-column table never moved. The newer filesystems even bend the rules in interesting ways: Btrfs does copy-on-write snapshots that share blocks, so df can show "used" space that doesn't quite mean what you think; ZFS has its own zfs list that's more honest about pools and datasets. When in doubt on those, trust the filesystem-native tool over df — but df still gets you 95% of the answer in one second.

See Also

  • du — the files view; pair with df for "filesystem full vs which directory"
  • lsof — find deleted-but-still-open files when df and du disagree
  • lsblk — the hardware view: physical disks and their partitions
  • mount — what's mounted where, and how
  • tune2fs — tune ext4 (reserved-blocks percent, label, journal options)
  • mkfs.ext4 — set the inode count when creating a filesystem
  • find — hunt the millions-of-tiny-files inode hog
  • /proc/mounts — where the mount list actually lives
  • inode — the per-file entry that's the other way a disk fills up
  • block device — what the Filesystem column really points at
  • LVM — when one filesystem spans many disks
  • disk full — the full diagnose-and-fix walkthrough

Tired of finding out the disk was full because the database crashed?

CleverUptime watches every filesystem on every server every minute, separates the real disks from the tmpfs noise, warns you on bytes and inodes, and tells you in plain language which mountpoint is filling up and how fast — before the pager goes off.

Want to see your own server's health right now? One command, no signup, no install.

Check your server →