ext4: Explanation & Insights

The filesystem under almost every Linux server you'll ever touch — boring, fast, and quietly bulletproof.

What It Is

ext4 — the fourth extended filesystem — is the default on-disk filesystem for Debian, Ubuntu, and the overwhelming majority of Linux servers running today. When you spin up a VPS, install a distribution onto bare metal, or format a fresh data disk without thinking too hard about it, ext4 is what you almost certainly end up with. It is the layer that turns a raw block device — a slab of numbered sectors with no notion of files — into something you can name, organise, and trust through power cuts and crashes.

If you've only ever written application code, you've probably never given the filesystem a second thought, because it just works. That's the point. ext4 is engineered to be invisible: predictable performance, fast recovery, mature tooling, and three decades of accumulated bug fixes behind it. It's the storage equivalent of TCP — nobody marvels at it, everyone relies on it, and that quiet dependability is precisely the achievement.

This page goes deep. We'll cover how ext4 lays data out on disk, what its journal actually protects (and the part that trips up nearly everyone — what it does not), the three data modes you can choose between, the 5% reservation that explains why df and du sometimes seem to lie to you, the inode count that's frozen at format time, and exactly which commands you reach for to create, inspect, tune, and repair an ext4 volume. By the end you'll know why it's the safe default — and the handful of cases where I'd reach for something else instead.

Why It Matters

The filesystem is the single most consequential decision you make when you format a disk, because you make it once and live with it for the life of the volume. Changing it later means backing everything up, reformatting, and restoring — in practice, building a new server. Get it wrong and you inherit the consequences for years.

For the vast majority of Linux servers, the right answer is ext4, and it's worth being clear about why rather than treating it as a reflex. ext4 isn't the fastest filesystem in every benchmark, nor the most feature-rich. What it is, is the most predictable. Its performance is steady rather than spiky. Its crash recovery is fast and reliable. Its repair tooling — e2fsck and friends — is the most battle-hardened in the entire Linux ecosystem, because every edge case worth hitting has been hit and patched somewhere across thirty years and untold millions of deployments. Every admin knows it. Every backup tool, every cloud image, every monitoring agent supports it. When something goes wrong at 3 a.m., ext4 is the filesystem most likely to behave the way the documentation says, and that is worth more than a few percent of throughput.

That's the headline opinion of this page: unless you have a specific, articulable reason to choose something else, choose ext4. Later on I'll tell you what those reasons look like — very large volumes, snapshot-heavy workflows, checksumming paranoia — but the default lives here, and it's a good one.

The ext Family in One Breath

ext4 didn't arrive from nowhere; it's the third draft of the same good idea, and understanding the lineage makes the whole design click into place.

  • ext2 (1993) gave Linux a real, native filesystem. Solid, simple — and crucially, no journal. After an unclean shutdown it had to scan the entire disk to check for damage, which on a big volume meant watching fsck crawl for an hour while the server sat there not booting.
  • ext3 (2001) bolted a journal onto ext2 without changing the on-disk layout — so much so that you could (and people did) flip a volume between the two formats. The journal made crash recovery near-instant. Same shape, one transformative addition.
  • ext4 (2008) is the rework. It keeps the journal but adds extents (a far more efficient way to track where a file's data lives), delayed allocation (decide block placement late, lay data down in big contiguous runs), multi-block allocation, and dramatically larger limits — files up to 16 TB, volumes up to 1 EB (exabyte).

So when this page says ext4 is mature, it's not marketing: the core data structures trace directly back to 1993, and the journal to 2001. ext4 is what you get when one design is refined, in place, across three eras of hardware. If you land on a server running ext3 today, upgrading it to ext4 in place is a two-command job, because they're family.

How ext4 Works

Blocks, Extents, and Where Your Data Actually Lives

ext4 divides the volume into fixed-size blocks, almost always 4 KB on Linux — chosen to match the kernel's memory page size so the page cache and the disk speak the same unit. Every file occupies one or more whole blocks. A one-byte file still consumes a full 4 KB block, which is why a directory full of millions of tiny files burns far more space (and far more inodes) than the byte count suggests.

The clever part is how ext4 remembers which blocks belong to a file. Its ancestors used a scheme of direct and indirect block pointers — a list of every block, with lists-of-lists for big files. Workable, but for a large contiguous file it meant tracking thousands of individual block numbers, and that metadata sprawl encouraged fragmentation. ext4 replaced it with extents: instead of "block 1000, block 1001, block 1002, …," an extent says "blocks 1000 through 4095, all in one run." One compact record describes a huge swath of file. Fewer metadata reads, less fragmentation, and the allocator is actively nudged to lay data down contiguously.

Paired with that is delayed allocation — ext4's habit of not deciding where a write lands the instant your program calls write(). It holds the data in the page cache for a moment, gathers up neighbouring writes, and only then picks a nice contiguous home for the lot. The result is less fragmentation and faster writes. The cost — and we'll come back to this, because it's the muddy bit — is that "the data is written" becomes a slipperier statement than you'd expect.

Inodes — The File's Identity Card

Every file and directory on ext4 has an inode: a fixed-size metadata record holding the file's size, owner (uid/gid), permission bits, timestamps, and the pointers (now extents) to its data blocks. Notably, the inode does not hold the filename — that lives in the directory entry, which is just a map from a name to an inode number. That separation is exactly why hard links are possible: two names, two directory entries, one shared inode.

Here is the gotcha that bites people who've never run a server: on ext4, the total number of inodes is fixed when you format the volume and can never be raised afterwards. mkfs.ext4 creates roughly one inode per 16 KB of space by default. That's generous for normal workloads — but a mail spool, a Maildir, a package cache, or a build directory full of microscopic files can exhaust the inode table while df still cheerfully reports gigabytes free. The symptom is baffling the first time: touch newfile fails with "No space left on device" on a disk that's 40% full. The diagnosis is df -i; the disease is running out of inodes; the only real cure is to reformat with more inodes (mkfs.ext4 -N <count> or a denser -i bytes-per-inode). XFS, which allocates inodes dynamically, never has this problem — one of the few places it cleanly beats ext4.

Note

"Disk full" has two completely different causes on ext4: out of blocks, or out of inodes. They produce the identical error and the identical application failure, but df shows one and df -i shows the other. If a disk claims to be full and the space numbers look fine, check inodes before you do anything else — you'll save yourself a confused half-hour.

Journaling — What It Protects, and What It Doesn't

This is the section that earns the page, because it's the thing almost every tutorial leaves muddy, and once it's clear it stays clear.

Writing a single file is never one operation under the hood. ext4 has to touch several structures: the data blocks themselves, the inode (new size, new timestamps), the directory entry (if the file is new), and the free-space bitmap (those blocks are now taken). If the power dies between those updates, some land and some don't, and the on-disk structure is now internally contradictory — an inode that claims blocks the bitmap thinks are free, a directory entry pointing at an inode that was never written. That's corruption, and on the journal-less ext2 the only way to find and fix it was fsck walking every inode, block, and directory on the volume looking for contradictions. On a large disk: hours, every time, while the server refused to boot.

The journal fixes this with an idea lifted straight from database transaction logs. Before touching the real filesystem structures, ext4 writes a description of the intended change into a small dedicated area — the journal — and marks it committed. Then it applies the change to the live structures. If the machine crashes mid-write, recovery doesn't scan the disk; it just replays the journal: finish any transaction that was fully committed, discard any that wasn't. Recovery takes seconds, independent of volume size. That speed is the entire reason ext4 boots cleanly after a crash and ext2 sat there making you wait.

Now the part everyone gets wrong. The journal's job is to keep the filesystem structure consistent — not to guarantee your file contents survive a crash. Those are different promises, and conflating them is the classic mistake. A consistent filesystem after a crash means fsck is near-instant and the metadata makes sense. It does not mean the last few seconds of data you wrote are safely on the platter — by default they may simply be gone, and gone cleanly, with no error. The journal protects the bookkeeping, not the book.

This becomes concrete the moment you meet the three data modes.

The Three Data Modes

ext4 (and ext3 before it) lets you pick how much goes through the journal, via the data= mount option. The choice trades safety against speed, and the default is a carefully-judged middle.

  • data=ordered — the default, and almost always the right one. Only metadata is journaled. But ext4 adds one crucial ordering guarantee: it forces the file's data blocks out to disk before it commits the metadata transaction that references them. So after a crash you can never see an inode pointing at blocks that contain someone else's stale, deleted data (the security hole the next mode has). You might lose the last few seconds of writes, but you'll never read garbage where your file should be. Safe structure, sane ordering, full speed.

  • data=writeback — metadata is journaled, but there's no ordering between data and metadata flushes. The metadata can hit the disk before the data does. After a crash, an inode can legitimately point at blocks that were never actually written — meaning a file can come back containing whatever stale bytes those blocks held before (potentially fragments of a different, deleted file). The filesystem is "consistent" in the technical sense, but a freshly-written file may be full of junk. Marginally faster than ordered, genuinely riskier, and a quiet information-leak hazard. I would not run a server on it.

  • data=journal — full data journaling. Both metadata and file data go through the journal first. This is the strongest guarantee — a committed write is genuinely durable, no last-few-seconds loss — but every byte is written twice (once to the journal, once to its final home), which roughly halves write throughput and disables delayed allocation. It exists for the rare case where you truly cannot tolerate losing any acknowledged write and you'd rather pay in speed. On an ordinary server it's overkill.

Pro Tip

If you actually need a write to be on the platter — a database commit, a fsync'd log — that's the application's job, via fsync()/fdatasync(), not the filesystem's data mode. data=ordered plus an application that calls fsync at the right moments gives you durability where it matters without paying the double-write tax everywhere it doesn't. The journal keeps the filesystem honest; fsync keeps your data honest. Know which problem you're solving.

So: the journal makes fsck near-instant and your metadata bulletproof. It is not a force field around your file contents. For that you need fsync discipline in the app and a real backup on another machine — because no journal, in any mode, protects you from rm -rf, a bad migration, or a dying disk.

The 5% Reservation — Why df and du Disagree, and Why Root Can Still Write When "Full"

Here's a small piece of design that explains two separate mysteries at once, and it's one of my favourite quiet details in ext4.

When mkfs.ext4 formats a volume, it sets aside 5% of the blocks for the root user. Ordinary users and daemons hit "disk full" when 95% of the blocks are consumed; that last 5% is reachable only by processes running as root. This isn't waste — it's a deliberate safety cushion, and it does two jobs.

First, fragmentation control. ext4's allocator does its best work when it has free space to play with; once a volume is genuinely 100% packed, it's forced to scatter new blocks wherever they'll fit, and fragmentation spikes. Keeping a slice always free lets the allocator keep laying down clean contiguous extents.

Second — and this is the one that saves your evening — a full disk doesn't lock root out. When a runaway log fills the root filesystem and every normal process is failing with "No space left on device," root can still write, because root can dip into the reserved 5%. That means sshd can still spawn a shell, you can still log in, and you can still delete the offending file. Without the reservation, a disk-full event on / could leave you locked out of your own server, unable even to log in to fix it — a genuinely nasty corner.

This is also the answer to a question that puzzles people the first time they see it: why does the disk say "100% used" with space clearly still free, and why do df and du disagree? df reports against the user-visible capacity (it knows about the reservation and counts that last 5% as effectively unavailable, so it can read 100% while real free blocks remain), while du just adds up the bytes in actual files and has no idea the reservation exists. Different vantage points, different numbers — neither is wrong.

On a small root volume, 5% is reasonable. On a multi-terabyte data volume that root never needs to rescue, 5% is tens of gigabytes left permanently on the table for no benefit. Dial it down with tune2fs -m 1 /dev/sdb1 (1% is plenty on a big data disk) — but don't set it to zero. The one time you fill that disk, you'll wish you'd left yourself a key to the door.

Backstory

The 5% number is a holdover from an era of much smaller disks, when 5% of a few hundred megabytes was a sensible emergency reserve and the fragmentation argument was sharper. The default never changed, so on a modern 4 TB volume the filesystem is quietly holding back 200 GB by inheritance. It's harmless on /, wasteful on bulk storage — which is why tune2fs -m is one of the first things experienced admins run on a fresh data disk.

How I Inspect It

Three tools answer almost every ext4 question, in roughly the order you reach for them.

Space and Inodes — df

df (disk free) is the daily driver. The -h flag makes the numbers human-readable, -T shows the filesystem type:

df -hT
Filesystem     Type   Size  Used Avail Use% Mounted on
/dev/sda2      ext4   234G   89G  133G  41% /
/dev/sdb1      ext4   1.8T  1.2T  551G  69% /data

And the one people forget until it bites them — df -i for inode usage:

df -i /
Filesystem      Inodes   IUsed   IFree IUse% Mounted on
/dev/sda2     15564800  412980 15151820    3% /

If IUse% is near 100% while Use% is comfortable, you've hit inode exhaustion, not a space problem. Same error, completely different fix.

The Superblock — tune2fs and dumpe2fs

tune2fs -l prints the ext4 superblock — the volume's own record of itself: block size, block and inode counts, the reserved-blocks percentage, the journal's presence, the UUID, the mount count, and the feature flags that distinguish ext4 from ext3:

tune2fs -l /dev/sda2
Filesystem volume name:   <none>
Filesystem UUID:          a1b2c3d4-e5f6-7890-abcd-ef1234567890
Filesystem features:      has_journal ext_attr resize_inode dir_index extent 64bit
                          flex_bg sparse_super large_file huge_file dir_nlink extra_isize
Default mount options:    user_xattr acl
Filesystem state:         clean
Inode count:              15564800
Block count:              62258688
Reserved block count:     3112934
Block size:               4096
Reserved blocks uid:      0 (user root)

That extent flag in the feature line is the single bit that says "this is ext4, not ext3" — ext3 has has_journal but not extent. Reserved block count is the 5% cushion in raw blocks; Filesystem state: clean means it was unmounted properly and needs no recovery. The richer cousin, dumpe2fs, dumps the same superblock plus every block group's layout when you need to go truly forensic.

tune2fs also changes these knobs without reformatting: tune2fs -m 1 to lower the reservation, tune2fs -c 30 to set a mount-count-based check interval, tune2fs -L mylabel to set a volume label, tune2fs -e remount-ro to make the volume flip to read-only on error instead of soldiering on through corruption.

The Repair Tool — e2fsck / fsck

e2fsck is the ext-family consistency checker; fsck is the generic front-end that dispatches to it. Critical rule: only ever run it on an unmounted (or read-only) filesystem — checking a live, mounted volume can itself cause the corruption you're trying to fix.

fsck -f /dev/sdb1      # -f forces a full check even if the FS thinks it's clean

In normal life you rarely run this by hand. After a clean shutdown ext4 is clean and skips the check. After a crash it replays the journal in seconds and moves on. The full surface scan only happens when the journal can't resolve things, when the mount count or time interval trips a scheduled check, or when you force it because something smells off — a volume that keeps going read-only, unexplained I/O errors, the aftermath of a failing disk. When e2fsck finds an inode it can't reconcile to any directory, it parks the file in lost+found under its inode number — the digital equivalent of a lost-property office.

Cheat Sheet

# --- Create ---
mkfs.ext4 /dev/sdb1                       # format with sensible defaults
mkfs.ext4 -L data /dev/sdb1               # with a volume label
mkfs.ext4 -N 4000000 /dev/sdb1            # force a specific inode count
mkfs.ext4 -i 8192 /dev/sdb1               # one inode per 8 KB (more inodes, for tiny files)
mkfs.ext4 -m 1 /dev/sdb1                  # reserve only 1% for root (big data volumes)

# --- Mount and persist ---
mount /dev/sdb1 /data                                       # mount now
blkid /dev/sdb1                                             # get the UUID for fstab
echo 'UUID=<uuid> /data ext4 defaults,noatime 0 2' >> /etc/fstab
mount -a                                                    # test fstab without rebooting

# --- Inspect ---
df -hT                                    # space + type, human-readable
df -i                                     # INODE usage — the "full but not full" check
tune2fs -l /dev/sdb1                      # superblock: block size, inode count, features
dumpe2fs /dev/sdb1 | less                 # full forensic dump (verbose)
lsblk -f                                  # device -> filesystem -> mountpoint tree

# --- Tune (no reformat needed) ---
tune2fs -m 1 /dev/sdb1                     # lower root reservation to 1%
tune2fs -L data /dev/sdb1                  # set/change label
tune2fs -c 30 /dev/sdb1                    # full check every 30 mounts
tune2fs -e remount-ro /dev/sdb1            # go read-only on error instead of continuing

# --- Check and repair (UNMOUNT FIRST) ---
fsck -f /dev/sdb1                          # forced full consistency check
e2fsck -p /dev/sdb1                        # automatic "preen" — fix safe problems silently

# --- Resize ---
resize2fs /dev/sdb1                        # grow to fill the partition (online)
resize2fs /dev/sdb1 100G                   # shrink to 100G (UNMOUNT FIRST)

# --- Data mode (in /etc/fstab options) ---
# defaults                                 # = data=ordered (the safe default)
# data=writeback                           # faster, riskier — don't
# data=journal                             # full data journaling, ~half write speed

Pro Tip

Always reference filesystems by UUID in /etc/fstab, never by /dev/sdX. Device names are handed out in kernel probe order — add a USB drive, swap a SATA cable, change a BIOS setting, and /dev/sda and /dev/sdb can quietly trade places. The next reboot then mounts the wrong filesystem on the wrong mount point, or refuses to boot at all. A UUID is baked into the superblock and never changes until you reformat. Get the UUID from blkid.

Tuning ext4 in Practice

A few mount options earn their keep on a server, set once in /etc/fstab:

  • noatime — the single highest-value flag. By default Linux updates each file's access timestamp every time it's read, which turns every read into a small hidden write — pointless overhead on a busy server where nothing actually consults the access time. noatime switches it off and can noticeably cut write traffic on read-heavy workloads. (relatime, the modern default, is a softer compromise that updates atime at most once a day; noatime is the cleaner choice on a server.) Add it to the options column in fstab.
  • errors=remount-ro — if the kernel detects filesystem corruption, immediately remount the volume read-only rather than keep writing into damaged structures. The read-only state is alarming, but it's the filesystem protecting your data from further harm. This is the default on / for most distributions, and it's the right one.
  • data=ordered — already the default; you rarely write it explicitly. Just don't change it to writeback chasing a few percent of speed.

On the format side, the choices that matter are the inode count (-N / -i, irreversible — get it right for your workload up front) and the reserved-block percentage (-m, but adjustable later with tune2fs, so less critical to nail at format time).

When I'd Reach for Something Else

ext4 is my default and should be yours, but defaults have edges. The honest exceptions:

  • Volumes above ~16 TB, or heavy parallel large-file I/O (media stores, backup targets, log aggregators): XFS. Its allocation-group design parallelises across CPUs beautifully, and it scales to volumes where ext4 starts running out of address space. RHEL, Rocky, and AlmaLinux default to it for exactly these reasons.
  • You genuinely want snapshots and per-block checksums — rolling back a bad upgrade, detecting silent bit-rot: btrfs or ZFS. Both are copy-on-write, so they sidestep journaling entirely. Both come with real caveats (btrfs and databases don't mix; ZFS lives outside the mainline kernel and wants RAM), so reach for them deliberately, not by default.
  • You'd otherwise hit inode exhaustion on a workload of countless tiny files and can't predict the count: XFS's dynamic inode allocation removes the foot-gun ext4's fixed count creates.

Notice none of these is "ext4 is too slow" or "ext4 is unreliable." It rarely is either. The reasons to leave are about scale and features, not soundness — which is the whole case for it being the default.

Migrating from ext3 or ext2

Because the three are family, you can convert an ext3 (or ext2) volume to ext4 in place, without reformatting, by switching on the ext4 feature flags and then forcing a check:

umount /dev/sdb1
tune2fs -O extents,uninit_bg,dir_index /dev/sdb1
fsck -f /dev/sdb1

Existing files keep their old block-pointer layout (only new files get extents), so it's not as clean as a fresh format, but it's instant, reversible in spirit, and gets you the journal-plus-extents feature set without moving a byte of data. Update the fstab type from ext3 to ext4 and remount. On older GRUB setups you'd sometimes leave /boot as plain ext2 for bootloader simplicity; modern GRUB reads ext4 without complaint, so even that holdout is fading.

Gotchas

Roughly in order of how often they catch people:

  • Inode exhaustion masquerades as a full disk. df says 40% used, but every touch fails with "No space left on device." Run df -i. The inode count is fixed at format time and cannot be grown — the fix is delete files or reformat with more inodes. See out of inodes.
  • "100% full" with free space — and root can still write. That's the 5% reserved block count doing its job. Lower it on big data volumes with tune2fs -m 1, but never to zero. It's also why df and du report different numbers.
  • fsck on a mounted filesystem corrupts it. Never run fsck/e2fsck on a live volume. Unmount first, or boot from rescue media for the root filesystem. The tool even warns you — heed it.
  • The journal is not a backup, and not data protection. It keeps the structure consistent so fsck is fast. It does not protect file contents against a crash (you can lose the last seconds of writes), and it does nothing against deletion, a bad deploy, or a failing disk. You still need backups.
  • data=writeback for "speed" is a false economy. The throughput gain is marginal and the cost is that crashed files can come back full of someone else's stale, deleted bytes. Stay on data=ordered.
  • Forgetting noatime on read-heavy boxes. Every read silently becoming a tiny write adds up on a busy server. Add noatime in fstab.
  • /dev/sda is not the same disk after a reboot. Device names follow probe order. Always use UUIDs in /etc/fstab, fetched from blkid.

History and Philosophy

The ext lineage is Linux's own coming-of-age story. The first ext filesystem was written by Rémy Card in 1992 to replace the borrowed MINIX filesystem that Linus Torvalds had used to bootstrap the early kernel — MINIX capped filenames at 14 characters and volumes at 64 MB, limits Linux outgrew almost immediately. ext2 followed in 1993 and was good enough to carry Linux into the server room and keep it there for the better part of a decade.

The pain ext2 left was fsck: as disks grew, the post-crash full-disk scan grew with them, and an hour of downtime after every unclean reboot became intolerable. Stephen Tweedie's answer, shipped as ext3 in 2001, was the journal — and he made the deliberate, almost conservative choice to add it on top of ext2's exact on-disk layout rather than redesign. That conservatism is why an ext2 volume could be turned into ext3 (and back) by toggling a flag. It also slowed ext3 down on the largest volumes, because it inherited ext2's block-pointer scheme.

ext4 (2008, with the core work led by Theodore Ts'o, who has stewarded the ext filesystems for decades) is where the designers finally allowed themselves to change the data structures: extents, delayed allocation, multi-block allocation, and the larger limits. The interesting cultural detail is that ext4 was initially merged as an experimental filesystem, precisely because the ext maintainers were so allergic to risking the reliability reputation ext2/3 had earned. They'd rather ship slow-and-trusted than fast-and-untested — and that instinct is exactly why, fifteen-plus years on, ext4 is the filesystem you reach for when you want to stop thinking about the filesystem. Boring, on a server, is the highest praise there is.

See Also

  • filesystem — the big picture: what every filesystem does and how to pick one
  • ext3 — ext4's parent: same layout, journaling but no extents
  • ext2 — the grandparent: no journal, the one that made you wait for fsck
  • XFS — where to go for very large volumes and parallel I/O
  • btrfs — copy-on-write with snapshots and checksums, used with care
  • ZFS — the gold standard for data integrity, lives outside the mainline kernel
  • inode — the metadata record behind every file; its count is fixed at format time
  • journaling — the crash-recovery mechanism in depth
  • page cache — where delayed allocation buffers your writes
  • mounting — attaching the filesystem to the directory tree
  • mkfs.ext4 — create an ext4 filesystem
  • tune2fs — inspect and adjust ext4 parameters without reformatting
  • fsck — the consistency checker (e2fsck for the ext family)
  • df — space and inode usage on every mounted filesystem
  • out of inodes — full disk, plenty of space, no inodes left
  • disk full — the other full-disk emergency, about blocks
  • read-only filesystem — what errors=remount-ro triggers and why

Is one of your filesystems quietly filling up right now — blocks or inodes?

CleverUptime watches disk usage on every mounted filesystem across your servers, tracks the trend over time, and warns you before a volume tips over into full — so you find out from a dashboard, not from an application falling over.

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

Check your server →