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/hostsThat 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 problemAlways 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
| ext4 | XFS | Btrfs | ZFS | |
|---|---|---|---|---|
| Default on | Debian, Ubuntu | RHEL, Rocky, Alma | Fedora, openSUSE | TrueNAS, Proxmox |
| Snapshots | No | No | Yes | Yes |
| Checksums data | No | No | Yes | Yes |
| Built-in RAID | No | No | Yes | Yes |
| Compression | No | No | Yes | Yes |
| Grow while mounted | Yes | Yes | Yes | Yes |
| Shrink | Yes, unmounted | No, ever | Yes | No |
| Dynamic inodes | No | Yes | Yes | Yes |
| In the mainline kernel | Yes | Yes | Yes | No — licensing |
| Memory appetite | Low | Low | Moderate | High |
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?
| Situation | Use |
|---|---|
| Any ordinary server | ext4, or whatever your distro defaults to |
| Red Hat family | XFS — it is the default for good reason |
| Desktop where you want update rollback | Btrfs with snapper |
| A NAS or anything holding data you cannot lose | ZFS |
| Databases and VM images | ext4 or XFS — avoid copy-on-write, or disable it per directory |
| A USB stick for other machines | exFAT |
| An EFI system partition | FAT32 — 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/sdb1Never 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
| Option | Does |
|---|---|
noatime | Stop updating access times — fewer writes |
nofail | Boot even if this cannot mount |
ro | Read-only |
noexec, nosuid, nodev | Hardening for /tmp and removable media |
compress=zstd | Transparent 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
| Symptom | Cause |
|---|---|
| “No space left” with free space | Out of inodes — df -i |
| Deleted a huge file, nothing freed | A process still holds it — lsof +L1 |
| Cannot shrink the partition | XFS. It is not possible. |
Btrfs full but df disagrees | Snapshots hold the old blocks |
| ZFS breaks after a kernel update | Out-of-tree module not rebuilt |
| Machine drops to emergency mode | An fstab entry failed — add nofail |
| Permissions ignored on a USB stick | FAT and exFAT do not store them |
| 5% of the disk unaccounted for | ext4’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 want | Command |
|---|---|
| Which filesystem is this? | df -Th |
| Everything, with UUIDs | lsblk -f |
| Out of inodes? | df -i |
| Everything about one file | stat file |
| Inode numbers in a listing | ls -li |
| Deleted files still held open | sudo lsof +L1 |
| Check a filesystem | sudo fsck -f /dev/sdX — unmounted only |
| Grow ext4 / XFS | resize2fs / xfs_growfs |
| Real Btrfs usage | btrfs filesystem usage / |
| Test fstab safely | sudo mount -a |
Related reading
- Disks and mounting — partitions, fstab and UUIDs
- Checking disk space — df, du and the inode trap
- Links — hard links, and why inodes explain them
- The Linux filesystem — the directory tree these hold
- How Linux boots — fstab failures and rescue mode
- Automated backups — what journalling does not do for you
