ext4 & XFS
The two mainstream Linux filesystems — internals and trade-offs
On this page · 15 sections
Why These Two
ext4 and XFS are widely used, journaled Linux filesystems. They differ in on-disk design and operational behavior, especially around growth, shrinking, and repair.
ext4: Evolutionary Line
ext2
1993 — no journal, fast but fragile after crash
ext3
2001 — added journaling (JBD), otherwise same on-disk layout
ext4
2008 — extents, 48-bit block nrs, dir_index, delayed allocation, checksums
The ext4 driver can mount ext3 filesystems. The reverse is conditional: an older ext3 driver cannot safely mount an ext4 filesystem that uses incompatible ext4 features such as extents or 64-bit block numbers.
ext4 On-Disk Layout
ext4 divides the block device into block groups, typically 128 MiB each. Each group holds everything needed to allocate files near their metadata — a locality strategy inherited from BSD's FFS.
Anatomy of a Block Group
| Region | Purpose |
|---|---|
| Superblock (copy) | Filesystem-wide metadata. Not in every group — only in groups 0, 1, and powers of 3, 5, 7 (sparse_super). |
| Group descriptor table | Points to this group's bitmaps and inode table. Duplicated wherever the superblock is. |
| Block bitmap | One bit per data block: free or in use. |
| Inode bitmap | One bit per inode slot: free or in use. |
| Inode table | Array of fixed-size inode structures (default 256 B each). |
| Data blocks | The rest — where file contents live. |
Note
Why backup superblocks matter: if block 0 gets corrupted, you can recover by pointing e2fsck at a backup: e2fsck -b 32768 /dev/sdX. mke2fs -n prints the locations without actually creating the FS.
ext4 Features Worth Knowing
| Feature | What it does |
|---|---|
extents | Replaces ext3's triple-indirect block tree with (offset, length, physical_block) triplets. Lower metadata overhead, faster for large files. |
dir_index | Hashed B-tree for large directories. Without it, directory lookup is linear — miserable past ~10k entries. |
flex_bg | "Flexible block groups": consolidates bitmaps and inode tables from several adjacent groups into one place, reducing seeks. |
huge_file | Uses i_blocks in cluster units instead of 512 B sectors — allows files > 2 TiB. |
uninit_bg | Marks unused block groups so fsck can skip them — drastically faster checks on large empty filesystems. |
inline_data | Small files (< ~60 B) stored entirely inside the inode's block-pointer area. No data block needed at all. |
64bit | 48-bit block numbers, enabling filesystems larger than 16 TiB (up to 1 EiB theoretical). |
metadata_csum | CRC32c checksums on superblock, group descriptors, bitmaps, inode table, extent tree, directory blocks. Detects silent corruption. |
encrypt | Per-directory encryption (fscrypt). Same key API used by Android and Chrome OS. |
ext4 Tuning
# # Create a filesystem tuned for a specific workload# mkfs.ext4 -T largefile4 /dev/sdb1 # 1 inode per 4 MiB — huge media library# mkfs.ext4 -T news /dev/sdc1 # 1 inode per 4 KiB — Maildir/news spool# mkfs.ext4 -T largefile /dev/sdd1 # 1 inode per 1 MiB — sweet spot for bulk## # Explicit inode count override# mkfs.ext4 -N 5000000 /dev/sde1 # force 5M inodes regardless of size## # RAID stripe alignment (stride = chunk/block, stripe-width = stride * data disks)# mkfs.ext4 -E stride=16,stripe-width=64 /dev/md0Reserved Blocks
By default, ext4 reserves 5% of the filesystem for root. This prevents unprivileged users from filling the disk and starving systemd-journald, syslog, or anything else that must keep writing. On a 10 TiB data volume, this is 500 GiB wasted — adjust with:
# tune2fs -m 1 /dev/sda1 # drop reserve to 1%# tune2fs -m 0 /dev/sdb1 # no reserve (fine for data-only volumes)Tip
Rule of thumb: keep the 5% reserve on the root FS. Drop it to 0 on pure-data mounts (backups, media, object stores, container image layers).
ext4 Command Line
| Command | Use |
|---|---|
mkfs.ext4 | Create a new ext4 filesystem. |
tune2fs | Change parameters on an existing FS: reserve %, mount count, label, UUID, feature flags. |
resize2fs | Grow (online) or shrink (offline) the filesystem. Grow is routine; shrink requires unmount and is slower. |
dumpe2fs | Print superblock and group info — fantastic for forensics. |
debugfs | Interactive low-level repair/inspection shell. Undelete files, inspect inodes by number, dump blocks. |
e2fsck / fsck.ext4 | Check and repair. |
# dumpe2fs -h /dev/sda1 | head -30Filesystem volume name: rootLast mounted on: /Filesystem UUID: 3f9a1b6e-...Filesystem magic number: 0xEF53Filesystem revision #: 1 (dynamic)Filesystem features: has_journal ext_attr resize_inode dir_indexfiletype needs_recovery extent 64bit flex_bgmetadata_csum_seed sparse_super large_filehuge_file uninit_bg dir_nlink extra_isizeencryptInode count: 6553600Block count: 26214400Block size: 4096Reserved block count: 1310720...XFS: Scalable From Day One
XFS was designed by Silicon Graphics in 1993 for IRIX — specifically to serve TB-sized video-editing workloads on multi-CPU workstations. Ported to Linux in 2001, merged upstream in 2002. Its entire design is oriented around scalability and concurrency: B+trees everywhere, parallel allocation groups, dynamic inode allocation.
RHEL made XFS the default root filesystem in RHEL 7 (2014), reflecting a quarter-century of production use.
XFS Design Principles
Allocation Groups (AGs)
An XFS filesystem is carved into a small number (usually 4–32) of allocation groups. Each AG is effectively a mini-filesystem with its own free-space and inode indexes. Because allocations within different AGs don't contend for the same locks, XFS scales to many concurrent writers far better than ext4.
B+ Trees Everywhere
- Free-space by block number — locate free runs adjacent to existing data.
- Free-space by length — find a run of exactly N free blocks.
- Inode B+tree — track which inode slots are allocated (inodes themselves are assigned dynamically, no pre-allocation).
- Directory B+tree — O(log n) lookups in directories with millions of entries.
- Extent B+tree inside the inode — maps logical offsets to physical extents for large files.
Dynamic Inode Allocation
XFS does not pre-allocate inodes at mkfs time. New inodes are carved out of free space on demand. Practical consequences:
- You can't run out of inodes while having free space — a common ext4 failure mode.
- Deleted inode space isn't automatically reclaimed to the pool of data blocks until the chunk is empty (this is usually invisible in practice).
Metadata-Only Journal
XFS journals metadata only. File data is never journaled. Ordering is enforced by writing data blocks before the metadata that references them, similar to ext4's data=ordered. There is no data=journal equivalent — if you need that level of durability, put the workload on a FS that supports it, or use application-level fsync.
XFS Features
| Feature | What it does |
|---|---|
| Delayed allocation | Writes stay in memory; physical blocks aren't chosen until flush. Lets the allocator see the full size of the write and place it contiguously. Excellent for reducing fragmentation. |
| Online defrag | xfs_fsr (filesystem reorganizer) runs while mounted and writable. Walks files, rewrites fragmented ones into contiguous extents. |
| Reflinks | Copy-on-write copies within a file or between files: cp --reflink=always a b shares extents until one side writes. Used heavily by container storage drivers and backup tools (borg, restic). |
| Quotas | User, group, and project quotas. Project quotas set limits on arbitrary directory trees — the mechanism Kubernetes uses for enforced per-volume quotas on XFS PVs. |
| Realtime subvolume | Separate data region with no metadata — extents allocated in fixed-size contiguous chunks. Deterministic I/O latency for video/audio workloads. Rare in practice. |
| CRC32c metadata checksums | On by default since mkfs.xfs 3.2.3 (2014). Every metadata block is verified on read. Silent corruption produces an explicit error, not mysterious misbehavior. |
| Scalability ceiling | 500 TiB per filesystem on 64-bit systems is routine; 8 EiB theoretical. Directories with tens of millions of entries remain fast. |
XFS Tuning
# # Default mkfs is already sane. Tune only when needed.# mkfs.xfs /dev/sda1## # RAID alignment — sunit in 512B sectors, swidth = sunit * data disks# mkfs.xfs -d sunit=128,swidth=512 /dev/md0## # More allocation groups for a high-concurrency workload# mkfs.xfs -d agcount=32 /dev/sdb1## # Larger log buffer — reduces log commit frequency under heavy metadata load# mount -o logbsize=256k /dev/sda1 /data## # Inspect an existing XFS# xfs_info /datameta-data=/dev/sda1 isize=512 agcount=4, agsize=6553600 blks= sectsz=4096 attr=2, projid32bit=1= crc=1 finobt=1, sparse=1, rmapbt=0data = bsize=4096 blocks=26214400, imaxpct=25= sunit=0 swidth=0 blksnaming =version 2 bsize=4096 ascii-ci=0, ftype=1log =internal log bsize=4096 blocks=12800, version=2= sectsz=4096 sunit=1 blks, lazy-count=1realtime =none extsz=4096 blocks=0, rtextents=0XFS Utilities Cheat Sheet
mkfs.xfs— createxfs_info— likedumpe2fs -h, but for XFSxfs_admin— change label / UUIDxfs_growfs— online grow (no offline option)xfs_fsr— online defragmenterxfs_repair— offline checker/repair (XFS has nofsck.xfsthat does real work; the mount-time log replay handles most cases)xfs_db— low-level debugger, equivalent todebugfsxfs_quota— manage user/group/project quotas
Growing and Shrinking
Grow: online, with resize2fs /dev/sda1 after the underlying block device/LV has been enlarged.
Shrink: offline only — unmount, e2fsck -f, resize2fs <dev> <size>, then shrink the block device. Slow but supported.
Grow: online, with xfs_growfs /mount/point.
Shrink: general reduction is not supported. Recent upstream xfs_growfs can remove free blocks from the end of the last allocation group without removing that group; availability depends on the kernel, tools, and distribution. For a substantial reduction, migrate data to a smaller filesystem.
Warning
Plan XFS capacity conservatively: limited last-allocation-group shrinking does not replace a general shrink workflow. On RHEL 10 this capability is a Technology Preview. Check support on your exact system before planning to reduce a volume; data migration remains the reliable path for large reductions.
ext4 vs XFS — Side by Side
| Property | ext4 | XFS |
|---|---|---|
| Max filesystem size (practical) | ~50 TiB recommended | PB scale, tested to 500+ TiB |
| Max file size | 16 TiB | 8 EiB |
| Inodes | Pre-allocated at mkfs (can run out) | Dynamic (cannot run out independently) |
| Journaling | Metadata + optional data journaling | Metadata only |
| Allocation | Block bitmap + extent tree | B+ trees (free by block, free by length) |
| Concurrency | Good; serialized on group bitmap locks | Excellent; independent AGs |
| Delayed allocation | Yes (since 2.6.30) | Yes (original) |
| Reflinks (CoW) | No | Yes |
| Project quotas | Experimental / limited | First-class (used by K8s) |
| Metadata checksums | Optional (default on mkfs ≥ 1.43) | On by default |
| Shrink | Offline, supported | Limited last-AG reduction on supporting systems; no general shrink |
| Online grow | Yes (resize2fs) | Yes (xfs_growfs) |
| Small-file performance | Strong, especially with inline_data | Good; large inode size helps |
| Large-file throughput | Good | Excellent — the original design target |
Choosing Between Them
- It's a general-purpose volume < 16 TiB.
- You may need to shrink later.
- Workload is small files and you want
inline_data. - You want the absolute maximum ecosystem maturity on Debian/Ubuntu.
- You need
data=journalfor a paranoid durability requirement.
- The filesystem is > 16 TiB, or will grow large.
- Many concurrent writers (database storage, mail spools, object-store backends).
- Workload is large files (video, backups, databases).
- You need reflinks (container storage, dedup-aware backups).
- You need project quotas (Kubernetes XFS PVs).
- You're on RHEL-family and want the distro default.
Tip
Default heuristic: ext4 for the root filesystem and small data volumes, XFS for anything that will get large or concurrent. If you have no opinion, follow your distro's default.
Honorable Mentions
| FS | Status | Notes |
|---|---|---|
f2fs | Mainline, production on Android | Flash-Friendly FS. Log-structured design tailored for NAND erase-block behavior. Solid choice for low-end SSDs, SD cards, embedded devices. Rarely used for general-purpose servers. |
ReiserFS / Reiser4 | Deprecated | Innovative B+tree design in the early 2000s, later abandoned. Removed from mainline Linux in 6.13. Not a choice for new deployments. |
bcachefs | Experimental (merged in 6.7, 2024) | CoW filesystem with built-in tiered caching, compression, snapshots, encryption. Ambitious — aims to be what btrfs was supposed to be. Not yet production-grade. Worth watching. |
Note
btrfs and ZFS are covered separately under Advanced Filesystems (storage-08). They're CoW filesystems with volume management, snapshots, and checksumming baked in — a fundamentally different architecture from ext4/XFS.
Takeaways
- ext4 and XFS are common Linux filesystem choices. Both are journaled and extent-based.
- The ext4 driver can mount ext3; mounting an ext4 volume with an older driver depends on its enabled features.
- ext4 pre-allocates inodes (risk of
ENOSPCwith free space); XFS allocates them dynamically. - XFS journals metadata only. No
data=journalequivalent. - XFS has no general shrink operation. Limited last-AG reduction is version- and distribution-dependent; plan substantial reductions as migrations.
- Use reflinks (
cp --reflink) on XFS for fast CoW copies — huge win for backups and container layers. - Default heuristic: ext4 for small/shrinkable volumes, XFS for large/concurrent/large-file workloads.
- Evaluate other filesystems against the workload and the kernel and tools available on the target system.
References
- Linux kernel: ext3 and ext4 compatibility
- Linux kernel: ext4 feature flags
- xfsprogs: xfs_growfs(8)
- RHEL 10: limited XFS shrinking Technology Preview