February 28, 2026
I switched my home backup target to btrfs last year, mostly because btrfs send | btrfs receive is genuinely lovely once you wrap your head around it. Six months in, here's what I wish someone had told me on day one.
The marketing says "snapshots take no extra space". That's true at creation time, less true after months of churn. Every byte that's been overwritten in the live subvolume is still on disk as long as any snapshot still references it. If your retention policy keeps daily snapshots for a year, expect significant overhead on workloads with high write turnover.
If you turn on btrfs quota enable to track per-subvolume usage, balance operations get dramatically slower. On my home server a balance went from 40 minutes to over 4 hours. The btrfs project has been working on this for years; check current state before deciding. For now I keep quotas off and use du on the snapshots when I want a rough estimate.
To do incremental sends you need both ends to have the same read-only snapshot to use as a base. The first time I tried to set up incremental backups I'd been taking RW snapshots out of habit; I had to start over with a fresh full send. Mark the source-side snapshot read-only at creation:
btrfs subvolume snapshot -r /data /snapshots/daily-$(date -I)
If you've used cp --reflink to share extents between files (or if dedup tools have done it for you), running btrfs filesystem defrag can undo the sharing and balloon your apparent disk usage. The man page warns about this in small print. There's no easy way to defrag without losing the sharing — by design, defrag rewrites extents.
Took me embarrassingly long to internalize this. btrfs scrub reads every block, verifies checksums, and rewrites any blocks that fail using a good copy from another device — but only if there's another device. On a single-device array, scrub will report errors but it cannot fix them. The redundancy has to be there already.