Skip to content

rsync as a Backup Engine

Scope: the snapshot pattern — a full, browsable backup every run, at close to the cost of an incremental — and an honest list of what rsync does not do for you.

This chapter is the mechanics. The strategy — what deserves backing up, how many snapshots to keep, testing restores — is Backups for Mere Mortals; rsync is the engine that strategy can run on, and this is how the engine works.

The problem with a mirror

A mirror (previous chapters) keeps the destination equal to the source now. Delete a file by accident, mirror that evening, and the backup has helpfully deleted it too. Corrupt a file and the corruption is faithfully mirrored. A backup needs the past, not just the present.

The naive past — a full copy every day — costs a full copy every day. rsync's answer is --link-dest.

rsync -a --link-dest=<previous snapshot> source/ <new snapshot>/

While syncing, rsync compares each file against the same file in the previous snapshot. If it's unchanged, it doesn't copy it — it creates a hardlink in the new snapshot to the old file. Both snapshots then name the same inode: one body of data, two directory entries, near-zero extra disk. Changed files are copied fresh, as their own.

From the lab — a small library, snapshotted, then one file edited and one added, and snapshotted again:

$ ls -li backups/2026-10-17_235122/reading.txt backups/2026-10-17_235124/reading.txt
5685588 -rw-rw---- 2 root root 13 ... backups/2026-10-17_235122/reading.txt
5685588 -rw-rw---- 2 root root 13 ... backups/2026-10-17_235124/reading.txt

$ ls -li backups/2026-10-17_235122/catalogue.txt backups/2026-10-17_235124/catalogue.txt
5685587 -rw-rw---- 1 root root 25 ... backups/2026-10-17_235122/catalogue.txt
5685595 -rw-rw---- 1 root root 45 ... backups/2026-10-17_235124/catalogue.txt

The unchanged file: same inode (5685588), link count 2. The edited file: different inodes, one copy per snapshot — exactly the semantics you want. And the disk tells the same story:

$ du -sh backups/2026-10-17_235122 backups/2026-10-17_235124
400K    backups/2026-10-17_235122
8.0K    backups/2026-10-17_235124

Each snapshot reads as a complete tree of about 400 KB; the second one costs 8 KB of real disk. Deleting an old snapshot frees only the files no other snapshot shares — the survivors keep their data until the last snapshot holding them goes.

The script

examples/snapshot.sh is the pattern as a tool — the exact script run for this chapter. It keeps a latest symlink so each run can find its predecessor for --link-dest:

#!/usr/bin/env bash
set -euo pipefail

src=$1
root=$2
stamp=$(date +%Y-%m-%d_%H%M%S)

mkdir -p "$root"
root=$(cd "$root" && pwd)   # --link-dest wants an unambiguous path

link_opt=()
if [ -L "$root/latest" ]; then
    link_opt=(--link-dest="$root/latest")
fi

rsync -a "${link_opt[@]}" "$src"/ "$root/$stamp/"
ln -sfn "$stamp" "$root/latest"
echo "snapshot: $root/$stamp"

Used as ./snapshot.sh library backups, it is the worked example's step 4 with a memory. Note what the script does not pass: --delete. A snapshot destination starts empty; there is nothing to delete, and adding it would only invite accidents.

What rsync is not

Stated plainly, because these surprises have names and dates in other people's post-mortems:

  • No versioning database. rsync keeps no history of its own. The snapshots are the history — a plain tree you can browse with ls. That's a feature until you want per-file history, search, or deduplication across unrelated trees; then you want a real backup tool (restic, BorgBackup — covered in the Backups guide).
  • Deletions and corruption propagate to mirrors. Snapshots quarantine the damage to "after the last good snapshot"; a plain mirror has no such mercy. Never let a mirror be your only copy.
  • No scheduling. rsync runs when something runs it. cron and systemd timers are the usual drivers; they are named here, not taught (documented).
  • No encryption or integrity archive. Snapshots are live files on a filesystem; bit-rot and ransomware reach them as easily as the originals. Off-site and offline copies remain a strategy question, not an rsync flag.
  • The same filesystem, please. Hardlinks only exist within one filesystem, so all snapshots in a set must live on the same volume — the script's single backup root enforces this by construction.

Used with its eyes open, though: a snapshot set is a backup you can inspect, restore from with cp, and reason about with du. Mortals have retired on less.