Backup & DRV · Linux File-Level Backup and RestoreFiles
rsync: what it is, and what it is not
What you'll learn
- Explain why the rsync delta algorithm makes a transfer cheap and what it costs at each end
- Enumerate what `rsync -a` carries and the four kinds of state it silently drops
- Select a flag set that reproduces a Linux tree faithfully and justify each flag from a measured difference
- Argue why a mirror maintained with `--delete` cannot recover from anything older than its last run
Prerequisites
Practice
Verified against restic 0.19.1 · BorgBackup 1.4.5 · rclone 1.75.0 · MinIO (S3-compatible object storage) RELEASE.2025-09-07T16-13-09Z · OpenZFS 2.4.1 · LVM2 2.03.31(2) · btrfs-progs 6.17.1 · PostgreSQL 18.6 · pgBackRest 2.59.1 · Kubernetes (k3s) and etcd k3s v1.36.3+k3s1, etcd 3.7.1 · Velero 1.18.2 · Docker Engine 29.7.2 · Proxmox Backup Server (documentation only) 4.0.10-1 · Ubuntu (host baseline) 26.04 LTS · 2026-08-28
Having settled what must leave a Linux host — payload data, identity databases,
package state, the configuration a service needs to start — the next question is
mechanical: what carries it, and what does the carrying preserve. Nearly every
estate reaches for rsync first, and rsync earns its reputation. It is also, in
the precise sense this course uses the word, not a backup system: not a
complaint about the tool but a statement about two properties it was never
designed to supply, history and independence from the thing it copies.
rsync solves a transfer problem, and it solves it extremely well
The problem rsync was written for is narrow and genuinely hard: make a destination tree equal to a source tree while moving as few bytes as possible. It attacks that twice. A file whose size and modification time match the destination’s is skipped and never opened; within files that did change, only the regions differing from the copy the receiver already holds are sent. A nightly run over a mostly static tree therefore moves almost nothing, because the volume sent tracks change rather than size.
That competence is the source of the confusion. rsync produces something that looks like a backup — a browsable tree, on another disk, with the right names and contents — and the resemblance is close enough that many estates stopped asking whether it behaves like one. It does not, and the first reason surfaces before history does.
What -a actually implies, and the four things it dropped
-a is a bundle, not a mode: the manual page defines it as the equivalent of
-rlptgoD — recurse, copy symlinks as symlinks, preserve permissions, times,
group and owner, reproduce device and special files. Absent are ACLs, extended
attributes, hard-link relationships and sparse-file handling.
In the archive-fidelity capture the source held app.conf with a POSIX ACL of 1
entry, index.dat with an extended attribute reading sha256:deadbeef,
netcheck carrying cap_net_raw=ep, admin-tool setuid, a sparse.img of
200M apparent and 0 allocated, and payload.a and payload.b sharing one inode
at link count 2.
$ rsync -a src/ r3/ [restored from rsync -a]
ACL on app.conf : 0 entr(y|ies)
xattr on index.dat : ABSENT
capability on netcheck : ABSENT
setuid bit on admin-tool: present
sparse.img apparent : 200M
sparse.img allocated : 200M
payload.a link count : 1
payload a/b same inode : NO - now two separate filesFour losses, each surfacing later rather than at copy time. With the ACL gone, effective permissions are whatever the mode bits alone say. With the capability gone, a binary allowed to open raw sockets without being setuid root fails at runtime, in the restored system. The sparse image now occupies 200M of real blocks that a restore target sized from apparent usage will not have.
The fourth surprises people: the link count is 1, and the capture records
NO - now two separate files. The data is intact twice over and the
relationship is destroyed, doubling the space and silently breaking whatever
depended on it. Note what survived: the setuid bit, because mode bits live
inside -p, inside -a. The boundary is not metadata versus content but an
enumerated list, and what falls outside is dropped silently.
$ rsync -aAXH --sparse src/ r4/ [restored from rsync -aAXH --sparse]
ACL on app.conf : 1 entr(y|ies)
xattr on index.dat : sha256:deadbeef
capability on netcheck : cap_net_raw=ep
setuid bit on admin-tool: present
sparse.img apparent : 200M
sparse.img allocated : 0
payload.a link count : 2
payload a/b same inode : yesThe same capture ran GNU tar 1.35 through the tree, and that comparison is easy
to state wrongly. Default tar -cf also lost the ACL, the attribute and the
capability, and also expanded the sparse file — 201M of archive against 100K for
tar --acls --xattrs --xattrs-include='*' --sparse. But it reported
payload.a link count : 2 and payload a/b same inode : yes, keeping the hard
link rsync -a broke. The defaults are different on one axis of four; tar is
not the safer tool.
The flag set to actually use, and why each letter is there
Every flag below is justified by a line in the capture, not by habit.
SRC=/srv/appdata/
DST=/backup/appdata/
rsync -aAXH --sparse --numeric-ids --one-file-system \
--exclude='*.sock' \
"$SRC" "$DST"
-A carries ACLs, without which the ACL arrived with 0 entries. -X carries
extended attributes, without which the user.* attribute and the
security.capability attribute holding cap_net_raw=ep were both absent — file
capabilities are stored as an extended attribute, which is why one flag recovers
both. -H preserves hard links, without which link count fell from 2 to 1.
--sparse keeps holes as holes, without which allocation went to 200M.
--numeric-ids is not in the capture but belongs here: the manual page
describes it as transferring numeric IDs rather than mapping them through names,
and on a repository host with its own /etc/passwd that mapping quietly
reassigns ownership to whatever local account shares the name.
--one-file-system stops traversal at mount points, keeping a copy of / out
of /proc and /sys. The trailing slash is load-bearing too: with one, the
directory’s contents are copied; without one, the directory itself is. And
preserving owner, or writing security.capability, needs privilege on the
receiving side.
--delete exists because a mirror is defined by equality
Without --delete, a repeatedly rsynced destination is not a copy of any point
in time. It is the union of every state the source has ever been in, with no
record of when anything left, so during a restore the live tree cannot be told
from the residue. --delete is what makes the destination equal the source —
and unavoidably what stops the result from being a backup.
The mirror-propagation capture runs the ordinary case: a nightly
rsync -a --delete src/ mirror/ at 01:00, established on night one as a
faithful copy of three files. On day 2 an operator removed a needed file.
$ rm src/invoice.txt$ rsync -a --delete src/ mirror/ (the scheduled 01:00 run)
--- mirror now ---
mirror/orders.csv
mirror/reports/q3.txt
invoice.txt recoverable from the mirror? NO - the mirror deleted it too
Elapsed time between the mistake and the loss of the only other copy:
one scheduled interval. Nobody had to make a second mistake.Nobody had to make a second mistake or ignore an alert: the deletion was
replicated by a correctly configured job doing what it was told. On day 3 the
capture ran the adversarial version — the source was encrypted in place, leaving
src/orders.csv.locked and src/reports/q3.txt.locked, and the job ran.
$ rsync -a --delete src/ mirror/--- mirror after that run ---
mirror/orders.csv.locked
mirror/reports/q3.txt.locked
--- is the mirrored ledger still readable business data? ---
0000000 S a l t e d _ _ < 370 351 357 256 N J t
0000020 363 225 5 016 V r 034 . \0 220 274 ] 345 237 033 &
plaintext copies of the ledger remaining anywhere: 0The capture’s own summary is the one to remember: the mirror did exactly what it was configured to do, on schedule, with exit code 0, and it did it to the only other copy of the data. No bug to fix and no flag to add: faithfulness is the feature.
One copy of the current state cannot answer a question about the past
Strip away the flags and the algorithm and the boundary is one sentence: rsync produces one copy of the current state.
A backup exists to answer questions of the form “give me this data as it was at time T”, where T is before now and usually before the damage was noticed. Corruption found on Thursday frequently began on Monday; a deletion is noticed a month later, when somebody looks. Each requires state older than the last run to exist somewhere, and a mirror has by construction none. Its recovery points do not number few; they number one, and it is the state you already have.
That is why the tool’s real place is underneath something supplying what it
lacks — a destination snapshotted after each run, or a repository tool storing
deduplicated versions. The manual page documents --link-dest, which hard links
unchanged files against a previous tree so each run yields a separate browsable
generation costing only the changed files, with the caveat that those
generations share a filesystem and a failure domain. The other missing property
is independence: a destination the source host can write to, over a credential
it holds, fails the single-event test.
What to take from this
- On rsync 3.4.1,
rsync -areportedACL on app.conf : 0 entr(y|ies)against a source with 1 entry, andxattr on index.dat : ABSENTwhere the source readsha256:deadbeef. - The same run reported
capability on netcheck : ABSENTwhere the source heldcap_net_raw=ep, so a binary allowed to run unprivileged fails at runtime in the restored system, not at copy time. sparse.img allocatedwent from0to200Mwhile apparent size stayed 200M, so a restore target sized from apparent usage falls short.payload.a link countfell from 2 to 1, recorded asNO - now two separate files; default GNU tar 1.35 kept that hard link, so defaults differ per tool.rsync -aAXH --sparsereproduced everything: 1 ACL entry,sha256:deadbeef,cap_net_raw=ep, allocated0, link count 2, same inodeyes.- On the
--deletemirror a deleted file wasNO - the mirror deleted it tooafter one scheduled interval, and after an in-place encryption theplaintext copies of the ledger remaining anywherecount was0.
Cross-course references
- Linux for Production Sysadmins — Part XXX (Linux Capabilities and
Privilege) explains what
cap_net_raw=epgrants and why a binary carries a capability instead of setuid root, which is what turns the measuredcapability on netcheck : ABSENTinto a service outage rather than a cosmetic metadata difference. - Ansible for Production Sysadmins — Part XVIII (Files and Configuration
Management) covers declaring which metadata a managed file must carry, and
that declaration is the reconstruction path for exactly the attributes
rsync -adropped here: the transfer carries data, configuration rebuilds the rest. - Ceph & Distributed Storage for Production Sysadmins — Part CVII (CephFS
Backup) applies file-level backup to a distributed filesystem, where these
same
-A,-Xand-Hquestions are answered against a filesystem whose metadata path is a separate service.
Quiz
Knowledge check · 5 questions
Q1. A tree holding a POSIX ACL, a user extended attribute, a binary carrying cap_net_raw=ep, a 200M-apparent sparse image and two paths sharing one inode is copied with `rsync -a`. What arrives?
Q2. A nightly `rsync -a --delete` mirror runs at 01:00. That afternoon an operator removes a file still needed, and nobody notices for a week. What does the mirror hold?
Q3. You are replacing `rsync -a` with a flag set that reproduces the source tree faithfully. Which additions are required by the measured differences? Select all that apply.
Q4. In the same capture, GNU tar 1.35 invoked with no metadata flags preserved a piece of state that `rsync -a` destroyed.
Q5. A team proposes replacing its nightly repository backup with an rsync --delete mirror to a second building, arguing it is off-site and checksum-verified. State the failure class it cannot recover from, and why the checksum does not help.
Passing score: 75%. Answers are checked in this browser.