Btrfs: Explanation & Insights

A filesystem that can snapshot itself in a heartbeat, checksum every block, and confuse the daylights out of df.

What It Is

Btrfs — the B-tree filesystem, and yes, half the internet pronounces it "butter-FS" while the other half says "better-FS" and a stubborn few insist on spelling out "bee-tree-eff-ess" — is Linux's ambitious, all-in-one storage layer. Where an older filesystem like ext4 does exactly one job (organise files on a single block device) and leaves redundancy to RAID and volume juggling to LVM, Btrfs tries to swallow all three jobs at once. It is a filesystem, a volume manager, and a software RAID engine fused into a single subsystem in the kernel. Snapshots, checksums on every block, transparent compression, multi-device pools, sending a filesystem over the network as a stream — all of it lives inside one tool.

That ambition is the whole story of Btrfs, both the good and the bad. When you want what it offers — cheap snapshots so you can undo a bad upgrade in one second, checksums that catch a disk quietly handing you wrong data, compression that shrinks a logs volume by half without you lifting a finger — there is nothing in the mainline kernel that competes. When you don't want those things, or when you point it at a workload it was never built for, it will punish you in ways that are genuinely hard to debug. This page is about learning where that line sits, so you put Btrfs where it shines and keep it well away from where it bites.

If you've only ever run ext4 — which is the right default for most servers, and which you should keep using until you have a reason not to — the mental shift Btrfs demands is this: it never overwrites your data in place. Not your files, not its own metadata, not the structures that track free space. Everything is written somewhere new and then the old pointers are swung over to the new location in one atomic step. That single design choice, copy-on-write, is the seed from which every Btrfs feature grows. Understand copy-on-write and you understand the entire filesystem — including the one thing that trips up almost everyone, which is why a Btrfs volume can swear it has free space and still refuse to let you write a single byte.

Why It Matters

The filesystem you format a partition with is a decision you make once and live with for years; redoing it means backing everything up, reformatting, and restoring. So choosing Btrfs — or choosing against it — is worth getting right, because the cost of being wrong is paid slowly, over the lifetime of the server, as fragmentation creeps in or as you discover the recovery story isn't what you assumed.

Here's the honest verdict up front, because a reference page should give you direction, not a shrug. Btrfs is excellent for snapshots, rollback, and NAS-style storage; it is a poor choice for the data volume under a busy database, and on filesystems with millions of files it tends to get slow and drive load up. If you want one-command rollback of a system upgrade, if you're building a file server where silent bit-rot would be a disaster, if you want compression on a heap of mostly-cold data — reach for Btrfs and enjoy it. If you're putting MySQL or PostgreSQL data files on a volume, or any workload that rewrites small chunks of large files all day long, do not use plain Btrfs; use ext4 or XFS, or at the very least turn copy-on-write off for that directory. We'll explain exactly why further down — the reason is the same copy-on-write magic that makes snapshots cheap, seen from its dark side.

The rest of this page teaches Btrfs from zero: how copy-on-write works and why it makes snapshots nearly free, what subvolumes are, how the built-in RAID stacks up (including the long-running RAID 5/6 caveat that you must not ignore), how checksums quietly save you from corruption, how compression works, and — the section worth the price of admission — why df lies to you on Btrfs and what to run instead.

How Copy-on-Write Works

Copy-on-write (universally abbreviated CoW) is one of those ideas that sounds almost too simple to be powerful. A traditional filesystem, when you change ten bytes in the middle of a file, finds the block holding those bytes and overwrites it in place. Btrfs never does that. To change those ten bytes, it reads the old block, makes a new block somewhere else with the modification applied, and then updates the metadata to point at the new block instead of the old one. The old block isn't touched — it just becomes unreferenced and is eventually reclaimed.

That sounds wasteful, and in the narrowest sense it is: a small write can trigger writing a whole new block plus a cascade of metadata updates above it. But it buys you three things that are otherwise expensive or impossible.

First, crash safety without a journal. ext4 survives a power cut by keeping a journal — a little log of intended changes it replays on the next boot (see journaling for the full story). Btrfs needs no such thing. Because it never overwrites live data, the old, complete, consistent version of the filesystem is always sitting on disk untouched until the new version is fully written and the single pointer at the top — the superblock — is flipped to point at the new tree. The flip is atomic. Either it happened, and you're on the new version, or it didn't, and you're on the old one. There is no "halfway." A power cut in the middle of a write loses the in-flight write, never the filesystem's integrity.

Second, snapshots that cost almost nothing, which get their own section below.

Third, end-to-end checksums, also below, because once you're rewriting blocks anyway, computing and storing a checksum alongside each one is nearly free.

The structure that makes all this manageable is the B-tree the filesystem is named after — a self-balancing tree that Btrfs uses for everything: the directory tree, the extent allocation map, the checksum table, the subvolume roots. When CoW updates a leaf at the bottom of a B-tree, it has to write a new copy of that leaf, then a new copy of its parent (pointing at the new leaf), then a new copy of its parent, all the way up to the root. This is the cascade that makes random writes expensive — tuck that thought away, because it's exactly the mechanism that makes Btrfs a bad bed for databases.

Note

"Copy-on-write" shows up all over computing, not just in filesystems — it's how fork() shares memory pages between a parent and child process until one of them writes, how container image layers stay small, how qcow2 disk images grow on demand. The same trick, everywhere: share the old version, and only pay to copy at the moment something actually changes.

Subvolumes

A subvolume is Btrfs's unit of "a filesystem within the filesystem." It looks like an ordinary directory when you browse into it, but under the hood it's an independent B-tree root with its own pool of files — and crucially, it's the thing you snapshot. You can mount a subvolume on its own, set quota on it, snapshot it without touching its siblings, and roll it back independently.

The pattern you'll see on a typical Btrfs root install (openSUSE does this out of the box, and it's the layout the whole snapshot-rollback workflow is built around) is to carve the system into subvolumes like @ for the root and @home for /home:

btrfs subvolume list /
ID 256 gen 4210 top level 5 path @
ID 257 gen 4198 top level 5 path @home
ID 312 gen 4205 top level 5 path @snapshots

Why bother splitting them? Because then you can snapshot and roll back the operating system — the @ subvolume — after a bad update, without rolling back everyone's home directories and emails to the same moment. The OS goes back in time; your data stays in the present. That separation is the entire reason subvolumes exist, and it's why a Btrfs rollback feels surgical instead of catastrophic.

Snapshots, and Why They're Nearly Free

Here's the payoff for all that copy-on-write machinery. A snapshot in Btrfs is just a new subvolume that starts out pointing at the exact same blocks as the original:

btrfs subvolume snapshot / /.snapshots/before-upgrade

That command returns instantly, even on a 2 TB filesystem, and consumes essentially zero extra space. Nothing is copied. The snapshot and the original share every block, and Btrfs simply remembers that both subvolumes reference them.

The magic is in what happens next. Because the filesystem is copy-on-write, the moment anything in the live filesystem changes, the changed block is written to a new location and the live tree points at the new one — while the snapshot keeps pointing at the old one. The snapshot grows only by the difference between then and now. Take a snapshot before a risky package upgrade, the upgrade rewrites a few hundred megabytes of binaries, and your snapshot costs you a few hundred megabytes — not a full second copy of the root filesystem. If the upgrade goes wrong, you roll the live subvolume back to the snapshot and reboot into the world exactly as it was before. This is the single most loved feature of Btrfs, and it's why distributions that use it ship with automatic pre-update snapshots: the safety net is so cheap there's no reason not to.

Pro Tip

A snapshot is not a backup. It lives on the same disks as the data it protects, so it saves you from a bad upgrade or a fat-fingered rm, but a dead drive or a fire takes the snapshot down with the original. Btrfs's btrfs send / btrfs receive pair exists precisely to fix this: it serialises a snapshot (or the difference between two snapshots) into a stream you can pipe to another machine, giving you cheap incremental off-box replication. Snapshots for "undo," send/receive for "disaster."

Built-in RAID, and the RAID 5/6 Caveat

Because Btrfs is a volume manager too, it can span multiple disks and add redundancy itself — no mdadm, no LVM underneath. You hand it several block devices and tell it a profile:

mkfs.btrfs -d raid1 -m raid1 /dev/sdb /dev/sdc

The profiles mirror the classic RAID levels, but with a Btrfs twist worth understanding:

  • single — no redundancy, data written to whichever device has room. The multi-disk equivalent of just bolting drives together.
  • raid0 — striping for speed, zero protection; any dead disk loses the lot.
  • raid1 — keeps two copies of every block on two different devices. Note this is not "mirror the whole disk" like classic RAID 1; Btrfs guarantees two copies somewhere across the pool, which means you can even build a redundant array from differently-sized disks, something traditional mirroring hates.
  • raid10 — striped mirrors, the usual blend of speed and safety.
  • raid1c3 / raid1c4 — three or four copies of every block, for the genuinely paranoid.

And here is the caveat you must not skip, because it's the one that has burned people. Btrfs's parity RAID — raid5 and raid6 — has been flagged as not production-ready for over a decade, and that warning still stands. It suffers from the classic "write hole": if power is lost partway through updating a stripe, the parity and data can end up inconsistent, and Btrfs's checksums can't always recover from it the way the design intends. The official Btrfs documentation has carried a stability warning on RAID 5/6 since the features first appeared, and the conventional, hard-won advice is simple.

Danger

Do not store data you care about on Btrfs raid5 or raid6. The parity-RAID code has known unresolved issues (the write hole, and historically buggy scrub/recovery behaviour) and has carried an official "unstable, not for production" warning for many years. If you need single- or double-disk-failure parity protection, run Btrfs raid1/raid10 for the small wins, or put Btrfs on top of a battle-tested mdadm array, or reach for ZFS and its RAIDZ — which solved the write hole by design. This is the single biggest foot-gun in the whole filesystem.

That last point deserves emphasis: the honest recommendation for parity redundancy under Btrfs is to let a layer that does it well handle it. Build an mdadm RAID 6, put a single-profile Btrfs on top, and you get Btrfs's snapshots and checksums with mature, trustworthy parity underneath. It's not as elegant as one unified tool, but it works, and "works" beats "elegant" at 3 a.m.

Checksums — Catching Silent Corruption

Every block Btrfs writes gets a checksum stored separately in a B-tree of its own. On every read, Btrfs recomputes the checksum and compares. If they disagree, the data on disk is wrong — it rotted, or the cable glitched, or the drive returned a neighbouring sector — and Btrfs refuses to hand you the bad data, returning an I/O error instead of silently lying to you.

This matters more than it sounds. A traditional filesystem has no idea whether the bytes a disk returns are the bytes it stored; it trusts the hardware completely. Most of the time the hardware is honest. But disks do occasionally flip a bit, mis-seek, or return stale data, and on a multi-terabyte volume holding data for years, "occasionally" becomes "eventually." This is silent corruption — silent because nothing alerts you; you just open a file one day and it's garbage, with no clue when it happened or why. Checksums turn silent corruption into loud corruption: an error you can see and act on.

And if you have redundancy — a raid1 profile, say — Btrfs goes one better. When a block fails its checksum, Btrfs fetches the good copy from the other device, hands that to your application, and rewrites the bad block from the good one. Self-healing, automatically, in the background. You find out it happened by reading the counters:

btrfs device stats /
[/dev/sdb].write_io_errs    0
[/dev/sdb].read_io_errs     0
[/dev/sdb].corruption_errs  3
[/dev/sdb].generation_errs  0

Three corruption errors caught and (if redundant) repaired. On a checksum-blind filesystem those three would have been three corrupted files you'd never have known about until far too late. To proactively walk the whole filesystem and verify every checksum — do this on a schedule, monthly is a sane cadence — run a scrub:

btrfs scrub start /
btrfs scrub status /

A scrub reads every block, verifies its checksum, and repairs from a good copy where it can. It's the Btrfs equivalent of a RAID array's periodic consistency check: find the rot before the rot finds you.

Transparent Compression

Btrfs can compress data on the way to disk and decompress it on the way back, invisibly to everything above it. You turn it on with a mount option and pick an algorithm:

mount -o compress=zstd /dev/sdb /data

The standout choice is zstd (Zstandard, Facebook's compressor), which hits a sweet spot the older options miss: it compresses nearly as fast as lzo but as densely as the slow-but-thorough zlib, and it accepts a level (compress=zstd:3) so you can dial the speed/ratio trade-off. On compressible data — logs, text, source trees, JSON — it's a free win: you store more in the same space and often read faster, because the disk is the bottleneck and there's now less to read off it. Btrfs is smart enough to detect incompressible data (already-compressed video, encrypted blobs) and skip wasting CPU on it.

Compression and CoW are friends, not rivals: both are about writing fresh blocks, and the compressor just sits in that write path. For a NAS or a logs volume, compress=zstd is one of the easiest performance-and-capacity wins Linux offers.

Why df Lies on Btrfs

This is the section that, if you remember nothing else, will save you a confusing afternoon. On Btrfs, the ordinary df command can tell you there's plenty of free space and the filesystem will still fail writes with ENOSPC — "No space left on device" — and you'll stare at it, certain something is broken. Nothing is broken. df is just answering a question that doesn't quite map onto how Btrfs allocates space.

Here's the mechanism. Btrfs doesn't manage raw blocks directly the way ext4 does. It first carves the raw disk into large (typically 1 GB) block groups, and — this is the crux — each block group is dedicated to one purpose: either data (your file contents) or metadata (the B-trees: directory entries, inode-equivalents, checksums, extent maps). A data block group cannot hold metadata, and a metadata block group cannot hold file data. They are separate allocations carved out of the same raw pool.

Now imagine a filesystem that has, over its life, allocated almost all of its raw space into data block groups, leaving little raw space un-carved. You delete a pile of files, freeing space inside those data block groups. df sees that freed space and cheerfully reports gigabytes available. But now you do something metadata-heavy — create thousands of small files, take a flurry of snapshots — and Btrfs needs a new metadata block group. There's no un-carved raw space left to make one (it's all committed to data block groups, even if those are half-empty), and metadata can't borrow space from a data block group. Result: ENOSPC. The disk is "full" of metadata room while sitting on heaps of free data room. df reported the data free space and never mentioned the metadata wall you just hit.

The fix is to ask Btrfs's own accounting, which understands block groups:

btrfs filesystem usage /
Overall:
    Device size:                 100.00GiB
    Device allocated:             98.00GiB
    Device unallocated:            2.00GiB
    Used:                         61.00GiB
    Free (estimated):             37.00GiB      (min: 36.00GiB)

Data,single:   Size:90.00GiB, Used:58.00GiB
Metadata,single: Size:8.00GiB, Used:3.00GiB
System,single:  Size:32.00MiB, Used:16.00KiB

Read it from the middle. Device unallocated is the raw space not yet carved into any block group — the only space from which a new metadata block group can be born. When that hits zero (or near it) while metadata is nearly full, you're about to hit ENOSPC no matter what cheerful number df shows. The Data and Metadata lines show the split: how much of each type is allocated versus actually used. The gap between "allocated" and "used" is the trap — space committed to one purpose that the other can't reach.

The cure is balance — Btrfs's tool for repacking block groups. A balance rewrites partially-full block groups into fewer, fuller ones, handing the freed-up raw space back to the unallocated pool where a new metadata block group can be allocated from it:

btrfs balance start -dusage=50 /

That says "repack any data block group that's under 50% full," which compacts the half-empty data block groups and returns raw space to the pool. After it finishes, Device unallocated climbs, metadata has room to grow again, and the phantom ENOSPC is gone.

Warning

Never let a Btrfs filesystem run to genuinely 0 bytes unallocated before reacting. Once a Btrfs volume is wedged in ENOSPC, even balance can struggle — it needs a little free space to do its repacking work, and if there's truly none, you can find yourself unable to delete files (because deletion is itself a metadata write that needs space). The escape hatch is to temporarily attach an extra device with btrfs device add, balance, then remove it. Easier to just watch disk usage and balance before you're against the wall.

So when someone says "Btrfs df is confusing," this is what they mean: df reports a single data/metadata-blind number, Btrfs allocates in purpose-locked block groups, and the two views diverge. Trust btrfs filesystem usage, not df, on Btrfs — and keep an eye on unallocated.

The Database Problem, and CoW Fragmentation

Now we can close the loop on the warning from the top of the page, because you have the mechanism in hand. A database file — an InnoDB tablespace, a Postgres heap — is one big file that gets rewritten in tiny pieces, constantly, all over its interior. On an overwrite-in-place filesystem that's fine: the ten bytes change where they sit, and the file stays one contiguous run on disk.

On Btrfs, copy-on-write turns every one of those tiny in-place rewrites into a new block somewhere else, plus the B-tree cascade above it. The database file, which started as one neat extent, shatters into thousands of scattered fragments — each little rewrite landing wherever there was free space, the original blocks left behind as holes. This is CoW write amplification and fragmentation, and on a busy database it's relentless: the file degrades a little with every transaction, sequential reads turn into seek-storms, and latency climbs and never recovers. Add snapshots into the mix and it's worse — a snapshot pins the old blocks, so even Btrfs's own defragment can't reclaim them without breaking the snapshot's sharing.

The fix, if you must keep the data on a Btrfs volume, is to tell Btrfs not to copy-on-write that directory:

chattr +C /var/lib/mysql

The +C attribute (capital C — set it on an empty directory so new files inherit it) disables CoW for those files, restoring overwrite-in-place behaviour. But note what you've given up: no checksums on those files, and snapshots of them stop being truly free. At that point you've turned Btrfs back into a plain filesystem for the one workload that mattered — which is the clue that you'd usually have been happier putting the database on ext4 or XFS in the first place and keeping Btrfs for the things it's actually good at.

The same fragmentation tendency, milder, applies to any file rewritten in place a lot — VM disk images, some torrent clients, large log files appended-and-rotated aggressively. And separately, Btrfs has a reputation for getting slow and load-heavy when a filesystem holds an enormous number of files: the B-trees grow deep, metadata operations multiply, and operations like balance or a full scrub start taking real time and pushing load up. None of this makes Btrfs bad — it makes it specialised. Put it where snapshots and integrity are the prize, not where raw random-write throughput is.

Cheat Sheet

# --- Create ---
mkfs.btrfs /dev/sdb                          # single-device btrfs
mkfs.btrfs -d raid1 -m raid1 /dev/sdb /dev/sdc   # two-copy redundancy across two disks
mkfs.btrfs -L data /dev/sdb                   # with a label

# --- Inspect (trust these, not df) ---
btrfs filesystem usage /                      # the REAL space picture (data vs metadata vs unallocated)
btrfs filesystem df /                         # per-type allocated/used summary
btrfs filesystem show                         # devices in each filesystem/pool
btrfs device stats /                          # per-device error counters (corruption, read, write)
btrfs subvolume list /                        # every subvolume and snapshot

# --- Subvolumes & snapshots ---
btrfs subvolume create /data/sub              # new subvolume
btrfs subvolume snapshot / /.snapshots/pre    # read-write snapshot (instant, near-zero space)
btrfs subvolume snapshot -r / /.snapshots/ro  # read-only snapshot (for send/receive)
btrfs subvolume delete /.snapshots/pre        # drop a snapshot

# --- Replication (off-box) ---
btrfs send /.snapshots/ro | btrfs receive /backup        # full
btrfs send -p /.snapshots/old /.snapshots/new | ...      # incremental (just the diff)

# --- Integrity ---
btrfs scrub start /                           # verify every checksum, repair from good copy
btrfs scrub status /                          # progress / results

# --- Space management ---
btrfs balance start -dusage=50 /              # repack <50%-full data block groups, free raw space
btrfs filesystem defragment -r -czstd /data   # defrag + recompress (beware: breaks snapshot sharing)

# --- Grow / shrink (online!) ---
btrfs filesystem resize +10G /data            # grow
btrfs filesystem resize -10G /data            # shrink (yes, btrfs can shrink online)
btrfs device add /dev/sdd /data               # add a disk to the pool
btrfs device remove /dev/sdc /data            # remove one (data migrates off first)

# --- Compression at mount ---
mount -o compress=zstd /dev/sdb /data         # transparent zstd compression

Pro Tip

Schedule a monthly btrfs scrub and watch btrfs device stats for rising corruption_errs. The whole point of checksums is that they let you see bit-rot — but only if you actually look. A scrub on a quiet Sunday turns "I'll find out when a file breaks" into "I found out and Btrfs already fixed it from the mirror."

Gotchas

Things that catch people, roughly in the order they'll bite you:

  • df undercounts or overcounts — always. Because of CoW block groups and shared snapshot blocks, df on Btrfs is approximate at best and misleading at worst. Use btrfs filesystem usage for the truth. (See the whole section above — this is the Btrfs gotcha.)
  • ENOSPC with free space showing. Metadata block groups filled up while raw unallocated space hit zero. Run a balance to free raw space back to the pool — and ideally balance before you're wedged, because a truly full Btrfs can be hard to dig out of.
  • Snapshots are not backups. They share disk with the original; a dead drive kills both. Use btrfs send/receive for real off-box copies.
  • Parity RAID is unsafe. raid5/raid6 carry a years-old "not for production" warning. Use raid1/raid10, or put Btrfs on mdadm, or use ZFS for parity.
  • Databases fragment horribly. CoW shatters rewritten files. Use chattr +C on the data dir, or better, keep the DB off Btrfs entirely.
  • Deleting files when full doesn't immediately free space. Deletion is itself a write, and with snapshots pinning blocks, removing a file may free nothing until the snapshot holding it is also gone. To recover space, you often have to delete the snapshots too.
  • btrfs check --repair is a last resort. Unlike a mature fsck on ext4, Btrfs's repair tool can make a damaged filesystem worse. Read-only btrfs check to diagnose; mount with recovery options or restore from backup before reaching for --repair.

History and Philosophy

Btrfs was started in 2007 at Oracle by Chris Mason, and its existence is a licensing story as much as a technical one. Sun Microsystems had built ZFS — a checksumming, snapshotting, pooled storage marvel — but released it under the CDDL, a licence legally incompatible with the Linux kernel's GPL. ZFS could never merge into mainline Linux. So the Linux world set out to build its own answer from scratch, GPL-clean, with the same modern feature set: copy-on-write, checksums, snapshots, integrated volume management. That answer was Btrfs.

It made it into the mainline kernel in 2009 and has been "stable for general use, with caveats" for well over a decade — a phrasing that captures its whole character. The core (single device, raid1, snapshots, compression, checksums) is genuinely solid and ships as the default root filesystem on openSUSE and was Fedora Workstation's default for years. The edges (parity RAID especially) lagged, and the reputation never fully shook the early years when Btrfs did eat filesystems. Today's Btrfs is far more dependable than its folklore suggests — but the folklore exists for a reason, and the parity-RAID warning is not folklore.

The deeper philosophical divide is the one that runs through all of modern storage: should a filesystem be invisible — do one job perfectly and get out of the way, like ext4 and XFS, leaving redundancy to RAID and volumes to LVM — or should it be a platform that owns the whole stack, like ZFS and Btrfs? There's no right answer, only trade-offs. The integrated approach gives you checksums end-to-end and snapshots that understand the volume layout, things a layered stack can't easily match. The layered approach gives you mature, independently-hardened pieces you can swap and reason about one at a time. Btrfs bet big on integration, delivered most of it, and is still — patiently, release by release — finishing the parts it promised. It's the cousin of ZFS: same ambitions, different licence, different pace, and a different set of sharp edges to respect.

See Also

  • filesystem — the big picture: what a filesystem is and how to choose one
  • ext4 — the boring, reliable default; what to use when you don't need Btrfs's tricks
  • XFS — the large-file workhorse, the other "just works" choice
  • ZFS — Btrfs's CDDL-licensed cousin, the gold standard for integrity, with parity that actually works
  • LVM — the volume manager Btrfs replaces with its own built-in version
  • device-mapper — the kernel layer LVM and friends are built on, contrasting with Btrfs's all-in-one approach
  • RAID — redundancy below the filesystem; what Btrfs tries (and partly fails) to do itself
  • inode — the metadata record Btrfs stores in its B-trees rather than a fixed table
  • page cache — where compressed Btrfs blocks live decompressed in memory
  • journaling — the crash-safety trick Btrfs sidesteps entirely with copy-on-write
  • mkfs.btrfs — format a device (or several) as Btrfs
  • df — shows space on every filesystem, but lies on Btrfs — use btrfs filesystem usage
  • disk full — the space emergency, with the Btrfs ENOSPC twist

Is a Btrfs volume about to wedge on ENOSPC while df swears it's fine?

CleverUptime watches the actual usage on every mounted filesystem and warns you as a volume trends toward full — before the metadata wall, the read-only remount, or the 3 a.m. page.

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

Check your server →