Skip to main content
RunBook Academy

Backup & DRV · Linux File-Level Backup and RestoreFiles

rsync: what it is, and what it is not

Intermediate⏱ ~28 min🧪 Lab requiredrsynctar

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

Not yet marked complete on this device.

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.

Data-loss riskrsync -a — the flag everyone uses
$ 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 files

Four 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.

Configuration changersync -aAXH --sparse — the same source, the same capture
$ 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 : yes

The 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.

Destructivethe 01:00 run, the day after an accidental deletion
$ 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.

Data-loss riskthe same job, the night after an in-place encryption
$ 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: 0

The 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 -a reported ACL on app.conf : 0 entr(y|ies) against a source with 1 entry, and xattr on index.dat : ABSENT where the source read sha256:deadbeef.
  • The same run reported capability on netcheck : ABSENT where the source held cap_net_raw=ep, so a binary allowed to run unprivileged fails at runtime in the restored system, not at copy time.
  • sparse.img allocated went from 0 to 200M while apparent size stayed 200M, so a restore target sized from apparent usage falls short.
  • payload.a link count fell from 2 to 1, recorded as NO - now two separate files; default GNU tar 1.35 kept that hard link, so defaults differ per tool.
  • rsync -aAXH --sparse reproduced everything: 1 ACL entry, sha256:deadbeef, cap_net_raw=ep, allocated 0, link count 2, same inode yes.
  • On the --delete mirror a deleted file was NO - the mirror deleted it too after one scheduled interval, and after an in-place encryption the plaintext copies of the ledger remaining anywhere count was 0.

Cross-course references

  • Linux for Production Sysadmins — Part XXX (Linux Capabilities and Privilege) explains what cap_net_raw=ep grants and why a binary carries a capability instead of setuid root, which is what turns the measured capability on netcheck : ABSENT into 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 -a dropped 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, -X and -H questions are answered against a filesystem whose metadata path is a separate service.

Quiz

Knowledge check · 5 questions

  1. 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?

  2. 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?

  3. 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.

  4. Q4. In the same capture, GNU tar 1.35 invoked with no metadata flags preserved a piece of state that `rsync -a` destroyed.

  5. 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.