Disk space on Linux: df, du and free, and why btrfs lies
See which disk is full, find what filled it, and understand why df, du and btrfs disagree.
Part 8 of 12 in Linux basics for self-hosters. Last updated
In short: df -h shows how full each mounted file system is; du -xh --max-depth=1 / | sort -h shows which directories use the space; free -h shows memory, where the available column is the one that matters. When df says full and du finds nothing, look for deleted files a program still holds open (sudo lsof +L1) and, on openSUSE's btrfs, for snapshots (sudo snapper list, sudo btrfs filesystem usage /).
df: how full is each disk?
df -h # every mounted file system, human units
df -h /srv # only the one that holds /srv
df -hT -x tmpfs -x devtmpfs # with file system type, without memory file systems
df -i # inodes: the number of files, not bytes Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 233G 201G 30G 88% /
/dev/sda1 3.6T 2.9T 700G 81% /srv/media
Read the Mounted on column first: it tells you which directory lives on which
disk. Use% above 90 on / deserves attention, because services start failing in odd
ways when the root file system is full. On ext4, Used plus Avail is a little less than Size: 5 %
is reserved for root by default. On btrfs, df -i shows zeros, because btrfs
creates inodes as needed. Paste your output into the
df, du and free explainer for a line-by-line
reading.
A real openSUSE laptop with the default btrfs layout. / and /home show
the same device and the same numbers, because they are subvolumes of one btrfs pool, not two
partitions. /tmp is a tmpfs in memory:
$ df -hT / /home /tmp
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/system-root btrfs 435G 311G 123G 72% /
/dev/mapper/system-root btrfs 435G 311G 123G 72% /home
tmpfs tmpfs 16G 3.1G 13G 20% /tmp Real output on openSUSE Tumbleweed, 2026-09-28; user and host names and addresses redacted.
du: what is using the space?
du -sh ~/Downloads # one total
du -h --max-depth=1 /var | sort -h # subfolders, largest last
sudo du -xh --max-depth=1 / | sort -rh | head -15 # the whole root disk, largest first -x keeps du on one file system, so a mounted NAS share or /proc does not
end up in the total. Walk down: run it on /, then on the biggest directory it shows,
and so on. To find single large files instead, use
sudo find / -xdev -type f -size +500M -exec ls -lh {} +, explained in
find and grep recipes.
free: is memory really low?
free -h
total used free shared buff/cache available
Mem: 15Gi 6.1Gi 1.2Gi 310Mi 8.3Gi 9.0Gi
Swap: 2.0Gi 0B 2.0Gi Look at available, not free. Linux fills idle memory with disk cache (buff/cache) and gives it back the moment a program needs it. A server with 1.2 GiB free and 9 GiB available is fine. Memory is really short when available stays low and swap used keeps growing. See free(1).
When df and du disagree
df counts blocks the file system has in use; du adds up the files it can see. Three things make them differ:
- Deleted files that are still open. Delete a 20 GB log while a service writes to
it, and the name is gone but the space is not.
sudo lsof +L1lists those files and the process holding each one; restarting that service frees the space. - Files under a mount point. Files written to
/mnt/naswhile the share was not mounted are hidden once it is mounted again. Unmount it and look, or bind-mount/somewhere else and run du there. - Snapshots. On btrfs, data kept alive by a snapshot is used space, but du only sees the current files. See the next section.
The first case, reproduced with a 200 MiB file held open by a sleep process: after
rm, du no longer counts it, but lsof shows the space is still taken, marked
(deleted). Stopping the process frees it.
$ head -c 200M /dev/zero > held.bin
$ sleep 600 < held.bin &
$ du -sh .
200M .
$ rm held.bin
$ du -sh .
0 .
$ lsof -nP +L1 | grep held.bin
sleep 3099381 user 0r REG 0,47 209715200 0 32734162 /home/user/scratch/held.bin (deleted)
$ kill %1 Real output on openSUSE Tumbleweed, 2026-09-28; user and host names and addresses redacted.
btrfs and snapshots on openSUSE
openSUSE installs its root file system on btrfs by default, and snapper takes a snapshot before and after every zypper run. That is what makes rollbacks possible, and it is also where "lost" space goes: when an update replaces 2 GB of packages, the old versions stay on disk inside the snapshot until it is deleted.
sudo btrfs filesystem usage / # the real picture: data, metadata, unallocated
sudo snapper list # snapshots with number, date and description
sudo snapper delete 412 # delete one snapshot (or a range: 400-410)
sudo snapper cleanup number # apply the configured cleanup rules now btrfs lives in /usr/sbin, so run it with sudo. Snapper deletes old
snapshots on a timer following the limits in /etc/snapper/configs/root; if the disk is
small, lower NUMBER_LIMIT there rather than deleting by hand every week. Keep
the most recent snapshots: they are your way back from a broken update.
The config file is readable without sudo. These are the limits on a Tumbleweed install: between 2 and 10 numbered (zypper) snapshots and 4 to 10 important ones, snapshots may use up to half the file system, and hourly timeline snapshots are off:
$ grep -E '^(NUMBER_LIMIT|NUMBER_LIMIT_IMPORTANT|TIMELINE_CREATE|SPACE_LIMIT)=' /etc/snapper/configs/root
SPACE_LIMIT="0.5"
NUMBER_LIMIT="2-10"
NUMBER_LIMIT_IMPORTANT="4-10"
TIMELINE_CREATE="no" Real output on openSUSE Tumbleweed, 2026-09-28; user and host names and addresses redacted.
Safe places to reclaim space
| What | Check | Trim |
|---|---|---|
| Journal | journalctl --disk-usage | sudo journalctl --vacuum-size=500M |
| Package cache | sudo du -sh /var/cache/zypp | sudo zypper clean -a, sudo dnf clean all, sudo apt clean |
| Container images | podman system df | podman image prune (add -a for every unused image) |
| btrfs snapshots | sudo snapper list | sudo snapper delete NUMBER |
| Your own downloads | du -h --max-depth=1 ~ | sort -h | By hand, after looking |
Rootless and rootful podman have separate storage, so run podman system df both as
your user and with sudo. For more space, a NAS share is often cheaper than a new disk; the
fstab generator for NAS shares writes the mount line.
Common mistakes
Deleting a log file a service still writes to
The space stays in use until the service restarts. Empty the file in place instead with
sudo truncate -s 0 /var/log/big.log, or better, let the journal or logrotate
manage it.
Deleting everything in /var/log or /var/lib
/var/lib holds databases, container storage and package manager state. Removing
things there by size breaks services and updates. Find the owner first
(rpm -qf or dpkg -S), then use that program's own cleanup.
Reading "free" in free -h as the memory left
Low free memory is normal and good: it means the cache is doing its job. Only a low available value, together with growing swap use, means the machine needs more memory or fewer services.
Frequently asked questions
Why does df show the disk as full when du shows much less?
Usually a program still has a deleted file open: the space is only freed when the program closes it. Find it with sudo lsof +L1 and restart that service. Other causes are files hidden under a mount point and, on btrfs, snapshots that du does not count.
What does the available column in free mean?
available is an estimate of how much memory new programs can get without swapping, including cache the kernel can drop. free is memory nobody uses at all, which is small on a healthy system because Linux uses idle memory as disk cache.
How do I see the size of a folder in Linux?
du -sh /path prints the total in human units. du -h --max-depth=1 /path | sort -h lists the subfolders from small to large. Add sudo for system folders, and -x to stay on one file system.
How do I free space on openSUSE btrfs?
Old snapper snapshots are the usual cause. List them with sudo snapper list, delete ones you do not need with sudo snapper delete NUMBER, and check the real usage with sudo btrfs filesystem usage /. The package cache (sudo zypper clean -a) and the journal (sudo journalctl --vacuum-size=500M) are the next places to look.