📸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 belegt984 MB186 MB197 MB65 MB1154 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-dest relativ angegeben gilt ab dem Zielverzeichnis – daher ../2026-09-23.
  • Nur wirklich identische Dateien (Inhalt, Zeit, Rechte, ggf. Besitzer) werden verlinkt – ohne -a bzw. mit -I nichts.
  • Niemals mit --inplace kombinieren: 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 -rf
Warum 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.