Sign in
← Linux: administration
11 min · easy · Lesson 6 of 10

Disk: df, du, mount, inode

Where disk space goes and why you get "disk full": df vs du, mount points, inodes, and deleted-but-open files.

"No space left on the disk"

The most common server incident: space runs out, and the service stops. The database won't write, the app crashes with No space left on device, logs cut off. Untangling it takes two commands: df (how much is used on each partition) and du (who's using it).

df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        40G   38G  512M  99% /
/dev/vdb1       100G   12G   83G  13% /var/lib/postgresql
tmpfs           2.0G     0  2.0G   0% /run

df -h shows partitions and how full they are in human-readable form (-h means K, M, G instead of 1-kilobyte blocks). The Use% column tells you right away where the trouble is: the root / is 99% full.

df vs. du

These are different tools, and people mix them up.

The combo works like this: df shows that / is full, then du finds the culprit.

du -sh /var/* | sort -h | tail -5
1.2G    /var/cache
3.4G    /var/lib
12G     /var/log

du -sh /var/* gives the size of each subdirectory (-s for one line per directory, -h in human-readable form). sort -h sorts taking the G, M, K suffixes into account, and tail -5 keeps the five biggest. The culprit here is /var/log at 12 gigabytes. You repeat the same trick one level down, du -sh /var/log/* | sort -h | tail -5, until you reach specific files.

Without sudo, du can't enter directories closed off from your user, prints Permission denied, and counts less than is actually used. On a server, run sudo du ....

Mount points

A partition becomes accessible once it's mounted at some point in the directory tree. The Mounted on column in df's output is exactly that: mount points. In the example above, /var/lib/postgresql is mounted from a separate disk, /dev/vdb1: the database data doesn't live on the root partition.

This matters when you're troubleshooting. Space can run out on / while /var/lib/postgresql still has plenty: they're physically different disks. df -h <path> shows which partition a specific path lives on.

df -h /var/lib/postgresql/16/main
Filesystem      Size  Used Avail Use% Mounted on
/dev/vdb1       100G   12G   83G  13% /var/lib/postgresql

lsblk shows the tree of disks and their partitions along with mount points. You can pick which columns to show with -o:

lsblk -o NAME,SIZE,TYPE,MOUNTPOINT
NAME    SIZE TYPE MOUNTPOINT
vda      40G disk
└─vda1   40G part /
vdb     100G disk
└─vdb1  100G part /var/lib/postgresql

You can see the physical disks (vda, vdb), their partitions (vda1, vdb1), and where each one is mounted. The mount command with no arguments prints all mounted filesystems along with their mount options (ro, noexec, size=..., and others).

One more detail in the same area. On ext4, 5% of blocks are reserved for root by default, so in df, Used and Avail add up to less than Size. A regular user hits "disk full" sooner than root does.

When df and du disagree

It happens: df says "full", but du on the same partition shows little used. A common reason is that a file was deleted while still open. A process holds a file open, someone deletes it (rm), and the space doesn't come back. The file has no name anymore, but the data stays on disk until the last process closes the descriptor. The typical case: someone deletes a bloated log by hand, while the app keeps writing to it.

Finding such files:

sudo lsof +L1
COMMAND PID USER   FD   TYPE DEVICE  SIZE/OFF NLINK NODE NAME
nginx   812 www    5w   REG  253,1  4294967296     0 1311 /var/log/nginx/access.log (deleted)

+L1 picks out open files with fewer than one link (the NLINK column equal to 0), that is, deleted but still open. The PID column has the process number holding the file, and SIZE/OFF shows how much space it takes up. You need root here: without it, lsof can't look into other users' processes.

In minimal images and containers, lsof is often not installed. The same descriptors are then visible straight from /proc: every process has a /proc/<PID>/fd directory, with a link for each open file. A link to a deleted file ends in (deleted).

sudo find /proc/[0-9]*/fd -lname '*(deleted)' 2>/dev/null
/proc/812/fd/5

The number after /proc/ is the PID. ps -o pid,cmd -p 812 shows which process it is, and ls -l /proc/812/fd/5 shows which file it's holding.

The space comes back once the process closes the file. Usually that means restarting the process (sudo systemctl restart nginx). The file itself won't come back, but the process will open a new one at the same path and write there.

A second cause of the mismatch: du can't see files under a mount point that has another partition mounted on top of it. The files still sit in the old directory and take up space on the parent partition, but while something else is mounted on top, neither du nor ls can see them.

Space is free, but files can't be created: inodes

Every file and directory uses up two resources on a partition: space for the data and an inode, a record about the file in the filesystem's internal table, holding the owner, permissions, size and a pointer to the actual data. The number of inodes is set when the filesystem is created (on ext4, with the mkfs.ext4 command) and doesn't grow afterward. If a partition has millions of small files (sessions, cache, queued emails), inodes run out before space does. A new file can't be created, with the same No space left on device error, while df -h still shows free gigabytes.

df -i shows how full the partition is by inode count:

df -i /var
Filesystem      Inodes   IUsed IFree IUse% Mounted on
/dev/vda1      2621440 2621440     0  100% /

IUse% at 100%: no more files can be created. du --inodes (in GNU du, that is, on Linux and WSL2) counts where most of them are:

sudo du --inodes -s /var/* | sort -n | tail -5
1843    /var/cache
21950   /var/lib
2597410 /var/spool

This kind of clutter is usually cleaned up by age, with find. For example, log archives older than 30 days:

sudo find /var/log/app -name '*.gz' -mtime +30 -delete

-mtime +30 means "modified more than 30 full days ago". A file exactly 30 days and some hours old won't match: find rounds the age down to whole days. Before -delete, it's worth running the same command without it first, to see what would get deleted.

Common mistakes

What you'll need for the tasks

The labs run in an ubuntu:24.04 container, where you're root, so you don't need sudo. The partition you'll be working on is mounted at /srv/data.

What to remember

df shows how full partitions are ("where it's full"), du shows directory sizes ("what ate it"). Troubleshooting goes from df to du.

Partitions are mounted at points in the tree, and space can run out on one partition while a neighboring one has plenty. df -h <path> tells you which partition you're dealing with.

If df and du disagree, look for deleted open files (sudo lsof +L1, or without lsof, sudo find /proc/[0-9]*/fd -lname '*(deleted)'). The space comes back once the process restarts.

No space left on device with space available means inodes ran out. Check df -i.

A full disk often means old logs, dumps or temp files were never cleaned up. The fix is rotation and scheduled cleanup, not a one-off rm.

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 3 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson linux-admin-06-disk-usage