MEASUREMENT
What backup compression actually buys a home server
Thirteen settings, five kinds of data, 282 MB. The slowest setting spends 360 times longer than the fast one to save an extra 8.0 MB.
Every backup tool asks you this question and none of them help you answer it. Borg wants a --compression value, restic wants a compression mode, ZFS wants a property on the dataset, and the forum answer is always somebody insisting that zstd-19 is obviously right because it compresses more.
It does compress more. The question is how much more, on the data you actually have, and what that costs you in the two places where it hurts: the length of the nightly backup window, and the length of the restore when something has gone wrong. Those are measurable, so here they are measured.
The short version
On a 282 MB corpus assembled to look like what a home server really stores, zstd -3 using all cores compressed the whole thing in 0.15 seconds and saved 118.3 MB. xz -9 took 54.7 seconds and saved 126.4 MB.
That is 360 times the wall clock for 6.8% more saved bytes — and the reason the gap is that lopsided is not that xz is bad. It is that half the corpus is photographs and video, and no setting compresses those at all.
-T0. Log scale, because the spread is three orders of magnitude. The annotation is how many megabytes each setting saved out of 282.The corpus
A benchmark on a Linux kernel tarball tells you how a codec handles source code, which is not what is on your NAS. This corpus was assembled to match the mix on a home server, and every part of it is fetched from a stable public URL by a script you can run, so the whole thing is rebuildable rather than merely described.
| Category | Size | What it is |
|---|---|---|
| code | 112.4 MB | The SQLite 3.46.0 source tree: C, headers, test scripts, makefiles |
| photos | 110.5 MB | 25 JPEG photographs, 2–6 MB each, from Wikimedia Commons |
| video | 30.7 MB | One 1080p H.264 clip (Big Buck Bunny, CC-BY, Blender Foundation) |
| db | 19.3 MB | A SQLite database built from the OurAirports open dataset, indexed and vacuumed |
| docs | 8.9 MB | Ten plain-text books from Project Gutenberg |
The photo sizes are deliberate: featured images on Wikimedia run to 20 or 30 MB, which no phone produces. Files in the 2–6 MB band are what a real library is full of.
How this was measured
Each category tar is fed to each codec on stdin, output captured to a file, then decompressed back and the restored size asserted against the original, so a setting that silently truncated would fail rather than post a good ratio. Timing is wall clock around the codec process. One run per pair, no median of repeats.
Machine: Apple M4, 10 cores, 16 GB, macOS 26.3.1, internal NVMe. zstd 1.5.7, xz 5.8.3, Apple gzip 475, bzip2 1.0.8. Settings marked -T0 use all ten cores; everything else is pinned to one, so the single-thread rows compare codecs and the -T0 rows show what the hardware adds.
Script: compress-bench.py. Corpus builder: build-corpus.sh. Raw output: compress-results.jsonl.
Ratio, by kind of data
| Setting | code | docs | db | photos | video |
|---|---|---|---|---|---|
| gzip -6 | 8.74x | 2.68x | 2.52x | 1.01x | 1.00x |
| gzip -9 | 8.96x | 2.69x | 2.54x | 1.01x | 1.00x |
| bzip2 -9 | 10.93x | 3.66x | 3.12x | 1.02x | 1.00x |
| xz -6 | 13.78x | 3.61x | 3.64x | 1.01x | 1.00x |
| xz -9 | 13.97x | 3.61x | 3.65x | 1.01x | 1.00x |
| xz -6 -T0 | 13.67x | 3.61x | 3.64x | 1.01x | 1.00x |
| zstd -1 | 8.36x | 2.42x | 2.27x | 1.00x | 1.00x |
| zstd -3 | 9.37x | 2.82x | 2.48x | 1.01x | 1.00x |
| zstd -9 | 11.53x | 3.13x | 2.73x | 1.01x | 1.00x |
| zstd -19 | 13.44x | 3.58x | 3.09x | 1.01x | 1.00x |
| zstd -19 --long=27 | 13.69x | 3.58x | 3.10x | 1.01x | 1.00x |
| zstd -3 -T0 | 9.37x | 2.82x | 2.48x | 1.01x | 1.00x |
| zstd -19 -T0 | 13.44x | 3.58x | 3.09x | 1.01x | 1.00x |
Three things fall out of that table.
Half your bytes are already compressed, and nothing changes that
Photos measured 1.01x at every single setting; video measured 1.00x at every single setting. JPEG and H.264 have already had the redundancy squeezed out of them, and a general-purpose compressor cannot find any more. xz -9 spent 30 seconds on the photo tar to save one megabyte out of 110.
On a real home server the imbalance is worse than in this corpus. Here photos and video are half the bytes; on an actual family NAS they are usually 80 or 90% of them, with a few gigabytes of documents and databases hiding underneath. Whatever ratio a compression setting achieves on the compressible remainder gets diluted by that dead weight before it reaches your disk usage.
On the data that does compress, the expensive settings win by less than advertised
Source code went from 9.37x at zstd -3 to 13.97x at xz -9 — a real improvement, roughly 49% better. But zstd -9 reached 11.53x at 209 MB/s, which is 24 times the throughput of xz -9 for two thirds of its gain. The database and the text behaved similarly: 2.48x to 3.65x for the database, 2.82x to 3.61x for the books.
The one genuine surprise: bzip2 -9 beat every other setting on plain text, at 3.66x against xz -9's 3.61x and zstd -19's 3.58x. It is the only category where the 1996 algorithm wins, and it wins by a margin too small to matter, which is a reasonable summary of bzip2's position generally.
Threads are free ratio, levels are not
zstd -3 compressed the source tree at 860 MB/s on one core and 3,078 MB/s on ten, at exactly the same 9.37x. Running zstd -19 with -T0 cut the whole-corpus time from 37.1 seconds to 20.4 while holding 1.80x. xz -6 -T0 did the same for xz, 46.0 seconds down to 23.1.
If you are choosing between a higher level and more threads, threads cost you nothing in ratio. Levels cost you throughput on a curve that goes vertical somewhere around zstd -10.
The number nobody checks: how fast does it come back?
Backup speed is the figure everyone compares. Restore speed is the one you find out about on the worst day of the year, and the spread there is wider.
| Setting | Decompress | Time to read back 2 TB |
|---|---|---|
| gzip -6 | 1,854 MB/s | 18 min |
| gzip -9 | 1,925 MB/s | 17 min |
| bzip2 -9 | 120 MB/s | 4.6 h |
| xz -6 | 292 MB/s | 1.9 h |
| xz -9 | 290 MB/s | 1.9 h |
| xz -6 -T0 | 604 MB/s | 55 min |
| zstd -1 | 2,300 MB/s | 14 min |
| zstd -3 | 2,169 MB/s | 15 min |
| zstd -9 | 2,596 MB/s | 13 min |
| zstd -19 | 2,524 MB/s | 13 min |
| zstd -19 --long=27 | 2,257 MB/s | 15 min |
| zstd -3 -T0 | 2,247 MB/s | 15 min |
| zstd -19 -T0 | 2,529 MB/s | 13 min |
bzip2 reads back at 119.6 MB/s on the code corpus and 32.4 MB/s on the photos. xz manages 290 MB/s. zstd decompresses at between 2,168 and 2,596 MB/s regardless of the level it was compressed at — a property of the format, not of the setting, and the reason it has taken over.
Scale that to a real restore. Reading 2 TB back through bzip2's 58 MB/s on mixed data is about ten hours of pure decompression before anything else in the restore has happened. Through zstd it is under an hour. That difference does not show up in any ratio table, and it is the one you will care about.
The caveat that matters here: on a NAS the disk or the network is usually slower than any of this. Over a gigabit link you get about 110 MB/s, so every setting above that is bottlenecked elsewhere and the ratio is all that is left to argue about. Below it — bzip2 on mixed data, xz on photos — the codec is genuinely the thing holding you up.
What to set
zstd -3with all threads for a general backup of mixed home-server data. It is within 5% of the best whole-corpus ratio measured here and it finishes before you have looked away.zstd -10to-12if the backup runs unattended overnight and the target is a slow or metered destination, where bytes cost more than minutes. Above that the curve stops paying.- Nothing at all for a dataset that is only photos and video. Compressing an Immich library is a CPU space heater. Both borg and restic will detect incompressible data and skip it, but "off" is still less work than "auto".
- Not bzip2, on the strength of the restore column alone.
- Not xz for backups, though it stays the right answer for a release artifact that is compressed once and downloaded a million times — a completely different trade from one that is compressed nightly and read once a year.
The backup window calculator runs these measured figures against a data mix and a time budget you enter, if you would rather see it applied to your own numbers than reason from the table.
What this does not tell you
- Real backup tools deduplicate first, and that is not measured here. Borg and restic split data into content-defined chunks of roughly a megabyte and compress each chunk on its own. That gives a compressor a far smaller window than a 112 MB tar stream does, so ratios on a real repository come out lower than these — most visibly for
--longsettings, which exist to exploit exactly the long-range redundancy that chunking removes. Deduplication across snapshots then saves far more than compression does, and it is the reason the second backup of a photo library is nearly free. - The corpus fits in RAM. These are CPU-bound figures with the files in the page cache. A NAS reading from spinning disks at 150 MB/s will not reach the fast numbers, and on that machine any setting above disk speed is indistinguishable from any other.
- One machine, one run per pair. An M4 is fast and has ten cores; a Raspberry Pi or an old N100 box will move every absolute number down and will punish the multi-threaded settings much less generously. The ordering should survive. The margins should not be quoted.
- No brotli, no lz4, no ZFS inline compression. brotli was not installed on the test machine; lz4 and ZFS both deserve a run of their own rather than a footnote here.
- Ratios depend on the corpus, and the corpus is a guess at yours. It is a defensible guess, and it is published so you can disagree with it precisely rather than in general.
Scripts, corpus builder and the full JSON output are on the raw data page. If your own numbers come out differently, send them — a result that contradicts this one is worth more than another that agrees.