📸Snapshot-Backups mit Hardlinks
Jeden Tag ein vollständiges Verzeichnis – und trotzdem kaum mehr Platz als ein einziges Backup? Mit--link-dest legt rsync unveränderte Dateien nicht neu an, sondern als Hardlink auf den Vortag. Das Prinzip steckt hinter rsnapshot und macOS Time Machine (HFS+-Variante).
🧮Snapshot-Simulator
Gleiche Farbe = gleicher Inode = dieselben Daten auf der Platte. Kräftig gefüllt: an diesem Tag neu geschrieben. Blass: nur ein weiterer Name (Hardlink).
| Datei | 📁 2026-09-20/ | 📁 2026-09-21/ | 📁 2026-09-22/ | 📁 2026-09-23/ | 📁 2026-09-24/ |
|---|---|---|---|---|---|
| db.sql | Inode 1001 neu geschrieben · 60 MB · Links: 1 | Inode 1005 neu geschrieben · 61 MB · Links: 1 | Inode 1007 neu geschrieben · 61 MB · Links: 1 | Inode 1010 neu geschrieben · 63 MB · Links: 1 | Inode 1012 neu geschrieben · 64 MB · Links: 1 |
| fotos.tar | Inode 1002 neu geschrieben · 800 MB · Links: 4 | Inode 1002 🔗 Hardlink · Links: 4 | Inode 1002 🔗 Hardlink · Links: 4 | Inode 1002 🔗 Hardlink · Links: 4 | Inode 1013 neu geschrieben · 950 MB · Links: 1 |
| mail.mbox | Inode 1003 neu geschrieben · 120 MB · Links: 1 | Inode 1006 neu geschrieben · 125 MB · Links: 1 | Inode 1008 neu geschrieben · 131 MB · Links: 2 | Inode 1008 🔗 Hardlink · Links: 2 | Inode 1014 neu geschrieben · 140 MB · Links: 1 |
| notizen.md | Inode 1004 neu geschrieben · 4 MB · Links: 2 | Inode 1004 🔗 Hardlink · Links: 2 | Inode 1009 neu geschrieben · 5 MB · Links: 1 | — | — |
| rechnung.pdf | — | — | — | Inode 1011 neu geschrieben · 2 MB · Links: 2 | Inode 1011 🔗 Hardlink · Links: 2 |
| neu belegt | 984 MB | 186 MB | 197 MB | 65 MB | 1154 MB |
5123 MB
so viel sähe
du --apparent-size je Snapshot zusammen (volle Kopien)2586 MB
tatsächlich belegt – jeder Inode zählt nur einmal
2537 MB
durch Hardlinks gespart (50 %)
Größen in MB sind Beispielwerte. „Links“ = Linkzähler des Inodes über die behaltenen Snapshots (stat -c %h). Beim Löschen eines Snapshots verschwinden nur Daten, deren Linkzähler auf 0 fällt – deshalb ist jeder Snapshot trotzdem vollständig wiederherstellbar. Nach einer Rotation „gehören“ dem ältesten behaltenen Snapshot alle seine Daten.
🔍So sieht es auf der Platte aus
zwei Läufe (rsync 3.5.0, Ausgabe gekürzt)
$ rsync -a daten/ snap/2026-09-23/ $ echo "Mail 2" >> daten/mail.mbox $ rsync -ai --delete --link-dest=../2026-09-23 daten/ snap/2026-09-24/ created directory snap/2026-09-24 >f.s....... mail.mbox $ ls -li snap/2026-09-23 snap/2026-09-24 snap/2026-09-23: 170621575 -rw-r--r-- 2 user staff 13 fotos.tar 170621576 -rw-r--r-- 1 user staff 7 mail.mbox snap/2026-09-24: 170621575 -rw-r--r-- 2 user staff 13 fotos.tar ← gleicher Inode, 2 Links 170621580 -rw-r--r-- 1 user staff 14 mail.mbox ← neu geschrieben
💡 Was ist ein Hardlink?
Ein Verzeichniseintrag ist nur ein Name, der auf einen Inode zeigt. Zwei Namen können auf denselben Inode zeigen – die Daten liegen einmal auf der Platte. Der Linkzähler (zweite Spalte bei
ls -l) zählt die Namen.⚠️ Stolperfallen
--link-destrelativ angegeben gilt ab dem Zielverzeichnis – daher../2026-09-23.- Nur wirklich identische Dateien (Inhalt, Zeit, Rechte, ggf. Besitzer) werden verlinkt – ohne
-abzw. mit-Inichts. - Niemals mit
--inplacekombinieren: das würde in verlinkte Dateien schreiben und alle Snapshots ändern. - Alle Snapshots liegen auf einem Dateisystem und Datenträger – ein Plattendefekt trifft alle. Zusätzlich extern sichern.
📜Ein tägliches Rotationsskript
#!/bin/sh
# Tägliches Snapshot-Backup mit Hardlinks (Beispielpfade)
set -eu
QUELLE="user@nas:/srv/daten/"
ZIEL="/backup/snapshots"
HEUTE=$(date +%F) # z. B. 2026-09-24
LETZTER=$(ls -1d "$ZIEL"/20* 2>/dev/null | tail -n 1 || true)
rsync -a --delete --numeric-ids \
${LETZTER:+--link-dest="$LETZTER"} \
"$QUELLE" "$ZIEL/$HEUTE.unvollstaendig/"
mv "$ZIEL/$HEUTE.unvollstaendig" "$ZIEL/$HEUTE" # erst nach Erfolg umbenennen
# nur die letzten 14 Snapshots behalten
ls -1d "$ZIEL"/20* | head -n -14 | xargs -r rm -rfWarum erst „.unvollstaendig“? Bricht der Lauf ab, sieht das halbe Verzeichnis nicht wie ein gültiger Snapshot aus. Der nächste Lauf verlinkt dann gegen den letzten vollständigen Stand.
Warum --numeric-ids? Beim Wiederherstellen auf einem anderen System sollen UID/GID erhalten bleiben, auch wenn dort andere Benutzernamen existieren.
Hinweis:
head -n -14 ist GNU-coreutils-Syntax (Linux). Das Ziel-Elternverzeichnis muss existieren – rsync legt nur die letzte Ebene selbst an.🗃️Alternative: --backup-dir
# Spiegel aktuell halten, Überschriebenes/Gelöschtes aufheben
rsync -a --delete --backup --backup-dir=../geaendert/2026-09-24 \
/srv/daten/ /backup/aktuell/Statt vollständiger Snapshots gibt es einen aktuellen Spiegel. Jede Datei, die rsync überschreiben oder löschen würde, wandert vorher nach
geaendert/2026-09-24/ (relativ zum Ziel!). Ohne --backup-dir hängt-b einfach ein ~ an den Namen.