A filesystem is the bookkeeping that turns a disk — which can only store numbered blocks of bytes — into directories and files with names, owners and dates. Almost nobody chooses one deliberately; you take whatever the installer offered and never think about it again.

That is usually fine. It stops being fine when a disk with free space refuses to accept a new file, when a deleted file does not give its space back, or when you need snapshots and discover your filesystem cannot do them.

Inodes: the file, minus its name

An inode holds everything about a file except its name: size, owner, permissions, timestamps, and which blocks on disk hold the data. The name lives in the directory, as an entry pointing at an inode number.

# Inode numbers
ls -li

# Everything an inode holds
stat /etc/hosts

That separation is not a technicality — it explains several behaviours that otherwise look like bugs.

A hard link is a second name for the same inode. Not a copy and not a pointer to a file — an equal name. Delete either one and the other is untouched, because a file is only really gone when its last name disappears and nothing has it open. Links covers the practical side.

Deleting a file a process still has open frees nothing. The name is removed, but the inode survives while a file descriptor references it, so df shows the space as used and du cannot find it. Restarting the process releases it — the case covered in ncdu vs du.

Renaming within one filesystem is instant regardless of size, because only the directory entry changes. Moving between filesystems is a genuine copy, which is why mv is sometimes immediate and sometimes takes ten minutes.

Running out of inodes

On ext4 the number of inodes is fixed when the filesystem is created. Millions of tiny files can exhaust them while gigabytes of space remain, and the error message is misleading:

$ touch newfile
touch: cannot touch 'newfile': No space left on device

$ df -h /
# Filesystem  Size  Used Avail Use%
# /dev/sda1   100G   40G   55G  42%     <- plenty of space

$ df -i /
# Filesystem  Inodes  IUsed IFree IUse%
# /dev/sda1     6.4M   6.4M     0  100%   <- the actual problem

Always run df -i alongside df -h when a disk-full error does not match what df -h says. The usual culprits are PHP session files, mail queues, or a cache directory nobody prunes.

# Which directory holds the most files?
sudo find /var -xdev -type f | cut -d/ -f1-4 | sort | uniq -c | sort -rn | head

The fix is deleting many files, not big ones. XFS and Btrfs allocate inodes dynamically and do not have this failure mode at all.

Journalling

Writing a file means several separate updates — the data blocks, the inode, the free-space map, the directory entry. Lose power halfway through and the filesystem is inconsistent: space marked used that nothing references, or a directory entry pointing at an inode that was never written.

A journal is a small log of intended changes, written before the changes themselves. After a crash the system replays or discards incomplete entries, which takes seconds. Without one, the alternative was checking every inode on the disk — hours on a large filesystem.

What journalling protects is the filesystem’s own structure, not your data. This distinction gets lost constantly. After a power cut you are guaranteed a mountable, consistent filesystem; you are not guaranteed that the document you were editing contains what you last typed. Journalling is not a substitute for backups, and nothing about it is.

Copy-on-write, and what it enables

Traditional filesystems modify blocks in place. Copy-on-write filesystems — Btrfs and ZFS — never do: a change is written to new blocks, then the pointers are updated. The old blocks stay until nothing references them.

That single design decision gives you three things almost for free:

  • Instant snapshots. Keeping the old pointers costs nothing until blocks change, so a snapshot of a 2 TB filesystem takes a second and no space.
  • Checksums on everything. Silent corruption is detected rather than quietly served, and with redundancy it is repaired.
  • No half-written state. A write either completed or it did not.

The costs are real too: fragmentation with databases and virtual machine images, more memory use, and disk-usage arithmetic that stops being intuitive once snapshots exist.

The four worth knowing

ext4XFSBtrfsZFS
Default onDebian, UbuntuRHEL, Rocky, AlmaFedora, openSUSETrueNAS, Proxmox
SnapshotsNoNoYesYes
Checksums dataNoNoYesYes
Built-in RAIDNoNoYesYes
CompressionNoNoYesYes
Grow while mountedYesYesYesYes
ShrinkYes, unmountedNo, everYesNo
Dynamic inodesNoYesYesYes
In the mainline kernelYesYesYesNo — licensing
Memory appetiteLowLowModerateHigh

ext4 is the safe default and the right answer for most servers. Two decades of production use, excellent tooling, entirely predictable. Its limitations are the fixed inode count and the absence of snapshots and checksums.

XFS is the Red Hat default and better than ext4 with large files and parallel writes. The catch is in the table and deserves emphasis: an XFS filesystem cannot be shrunk. Not with unmounting, not with tools — the only route is backup, recreate, restore. Plan partition sizes accordingly.

Btrfs brings snapshots, checksums and compression to the mainline kernel, which is why Fedora and openSUSE default to it on desktops. Snapshotting before an update and rolling back from the boot menu is genuinely valuable — particularly on Arch. Its RAID 5 and 6 modes are still not considered production-ready; RAID 1 is fine.

ZFS is the most capable and the least convenient. It combines volume management, RAID and filesystem into one coherent system with the best data-integrity story available. It is also not in the mainline kernel for licensing reasons, so it ships as a module that can break on a kernel upgrade, and it wants a lot of RAM. On a storage server it is worth every bit of that; on a laptop it is not.

So which one?

SituationUse
Any ordinary serverext4, or whatever your distro defaults to
Red Hat familyXFS — it is the default for good reason
Desktop where you want update rollbackBtrfs with snapper
A NAS or anything holding data you cannot loseZFS
Databases and VM imagesext4 or XFS — avoid copy-on-write, or disable it per directory
A USB stick for other machinesexFAT
An EFI system partitionFAT32 — required, not optional

The honest summary: if you do not have a specific reason to choose, the default is correct. Filesystem choice matters far less than having working backups, and time spent on the latter is better spent.

Practical commands

# What am I running?
df -Th
lsblk -f

# Space and inodes
df -h
df -i

# Check a filesystem - it MUST be unmounted
sudo umount /dev/sdb1
sudo fsck -f /dev/sdb1

# Grow ext4 (online) or XFS (must be mounted)
sudo resize2fs /dev/sdb1
sudo xfs_growfs /mnt/data

# Reserved space: 5% of a big disk is a lot to give root
sudo tune2fs -m 1 /dev/sdb1

Never run fsck on a mounted filesystem. It will report imaginary errors and, if you let it “fix” them, cause real ones. Unmount first, or boot from rescue media — see how Linux boots.

That tune2fs -m line is worth knowing: ext4 reserves 5% for root by default, which made sense on small disks and means 100 GB withheld on a 2 TB data volume. On a filesystem that holds no system files, 1% is plenty.

Mount options worth knowing

OptionDoes
noatimeStop updating access times — fewer writes
nofailBoot even if this cannot mount
roRead-only
noexec, nosuid, nodevHardening for /tmp and removable media
compress=zstdTransparent compression (Btrfs)

nofail is the one that saves you. Without it, a filesystem listed in /etc/fstab that fails to mount stops the boot dead — the single most common way to make a working machine unbootable. Put it on every non-essential mount, and test with sudo mount -a before rebooting. Disks and mounting covers fstab in full.

Things that catch people out

SymptomCause
“No space left” with free spaceOut of inodes — df -i
Deleted a huge file, nothing freedA process still holds it — lsof +L1
Cannot shrink the partitionXFS. It is not possible.
Btrfs full but df disagreesSnapshots hold the old blocks
ZFS breaks after a kernel updateOut-of-tree module not rebuilt
Machine drops to emergency modeAn fstab entry failed — add nofail
Permissions ignored on a USB stickFAT and exFAT do not store them
5% of the disk unaccounted forext4’s root reservation

The Btrfs one surprises people badly: delete 50 GB and gain nothing, because a snapshot still references those blocks. btrfs filesystem usage / tells the truth where df cannot, and the fix is deleting old snapshots.

This page is about the filesystem layer. It is one of seven the stack puts between your program and the disk, and several of the problems above are really problems in the layers above or below it — The Life of a Write follows a single write() down through all of them, from the page cache to the platter, with the command that inspects each.

Quick reference

You wantCommand
Which filesystem is this?df -Th
Everything, with UUIDslsblk -f
Out of inodes?df -i
Everything about one filestat file
Inode numbers in a listingls -li
Deleted files still held opensudo lsof +L1
Check a filesystemsudo fsck -f /dev/sdX — unmounted only
Grow ext4 / XFSresize2fs / xfs_growfs
Real Btrfs usagebtrfs filesystem usage /
Test fstab safelysudo mount -a

Related reading