Backups, die man auch zurückspielen kann: restic im Homelab
Seit Fotos, Dokumente und Passwörter bei mir im Homelab liegen, ist eine Frage plötzlich ziemlich wichtig geworden: Was passiert, wenn eine Festplatte stirbt? Oder ich aus Versehen das falsche Verzeichnis lösche? Oder die Wohnung abbrennt?
In meinem Homelab-Update habe ich kurz erwähnt, dass ich dafür restic nutze. In diesem Beitrag zeige ich, wie das genau aufgebaut ist, mit allen Befehlen zum Nachmachen.
Die 3-2-1-Regel
Die bekannteste Faustregel für Backups ist 3-2-1:
- 3 Kopien der Daten (das Original zählt mit)
- auf 2 verschiedenen Medien
- davon 1 außer Haus
Bei mir sieht das so aus:
- Original: auf der Docker-VM bzw. dem Raspberry Pi 5
- Lokale Kopie: auf meinem alten ReadyNAS, eingebunden per NFS. Davon kann ich schnell wiederherstellen, ohne erst alles aus dem Internet zu laden.
- Offsite-Kopie: auf einer Hetzner Storage Box (1 TB), per SFTP. Die schützt gegen Brand, Diebstahl oder Überspannung, also alles, was beide Geräte zu Hause auf einmal erwischt.
Ein RAID ist übrigens kein Backup. Wenn ich eine Datei lösche, löscht das RAID sie brav auf beiden Platten gleichzeitig.
Warum restic?
Ich habe mir ein paar Tools angeschaut (BorgBackup, Duplicati, Kopia) und bin bei restic gelandet, weil:
- alles clientseitig verschlüsselt wird. Hetzner sieht nur Datenmüll, nicht meine Fotos.
- es dedupliziert. Jede Nacht wird ein kompletter Stand gesichert („Snapshot“), aber nur die geänderten Teile werden wirklich übertragen. Das spart enorm viel Platz.
- es ein einzelnes Programm ohne Server ist und direkt SFTP kann. Auf der Storage Box muss ich also nichts installieren.
- die Doku sehr gut ist und es seit Jahren stabil läuft.
Installiert ist es schnell:
sudo apt install resticEin Repository anlegen
restic speichert alles in einem sogenannten Repository. Ich habe pro Host und pro Ziel eins, also z. B. für die Docker-VM eins auf dem NAS und eins auf der Storage Box.
Das Passwort für das Repository liegt in einer Datei, damit restic nicht jedes Mal fragt:
sudo install -d -m 0700 /etc/restic
openssl rand -hex 48 | sudo tee /etc/restic/password > /dev/null
sudo chmod 600 /etc/restic/passwordBei mir legt Ansible diese Datei aus einem verschlüsselten Secret im Repo an (wie das funktioniert, steht in meinem Beitrag über SOPS und age). Ganz wichtig: Dieses Passwort zusätzlich an einem sicheren Ort aufheben (Passwortmanager und offline). Ohne das Passwort sind die Backups wertlos, da hilft auch Hetzner nicht weiter.
Dann das lokale Repository auf dem NFS-Share anlegen:
sudo restic -r /mnt/backup/restic/docker-vm --password-file /etc/restic/password initDie Hetzner Storage Box per SFTP
Bei der Storage Box habe ich für jeden Host einen eigenen Sub-Account angelegt. Jeder Sub-Account sieht nur sein eigenes Verzeichnis. Falls also mal ein Server kompromittiert wird, kommt der Angreifer nicht an die Backups der anderen.
Damit restic sich per SSH-Key anmelden kann, bekommt root einen Eintrag in der SSH-Konfiguration:
# /root/.ssh/config
Host storagebox
HostName u123456-sub1.your-storagebox.de
User u123456-sub1
Port 22
IdentityFile /etc/restic/storagebox_keyDanach kann restic die Storage Box einfach über den Namen storagebox ansprechen:
sudo restic -r sftp:storagebox:restic --password-file /etc/restic/password initZusätzlich habe ich in der Hetzner Console automatische Snapshots der Storage Box eingeschaltet. Der Grund: Über SFTP kann restic nicht im „Nur-Anhängen“-Modus arbeiten. Wer den Server übernimmt, könnte also theoretisch auch das Backup löschen. Die Snapshots der Storage Box kann ein Sub-Account aber nicht löschen, damit gibt es noch eine zweite Sicherheitsebene.
Datenbanken: nicht einfach die Dateien kopieren
Das war für mich der lehrreichste Teil. Wenn man die Dateien einer laufenden Datenbank einfach kopiert, kann es sein, dass man sie mitten in einem Schreibvorgang erwischt. Das Backup ist dann im schlimmsten Fall kaputt, und das merkt man erst beim Wiederherstellen.
Deshalb erzeugt mein Backup-Skript vor jedem Lauf konsistente Kopien der Datenbanken in einem eigenen Ordner, und restic sichert dann diese Kopien.
Für SQLite (z. B. Vaultwarden, Uptime Kuma) gibt es dafür den eingebauten .backup-Befehl, der auch bei laufender Anwendung eine saubere Kopie macht:
sqlite3 /srv/appdata/vaultwarden/db.sqlite3 ".backup '/srv/backup-dumps/vaultwarden.sqlite3'"Für PostgreSQL (z. B. Immich, Paperless) läuft pg_dump direkt im Datenbank-Container:
docker exec paperless-db pg_dump --clean --if-exists --username=paperless paperless \
| gzip > /srv/backup-dumps/paperless.sql.gzDie eigentlichen Datenbank-Ordner schließe ich vom Backup aus, die bringen ohne Dump nichts.
Das eigentliche Backup
Was gesichert wird und was nicht, steht in einer Ausschluss-Datei. Rausfliegen zum Beispiel die Thumbnails von Immich (die kann Immich jederzeit neu erzeugen) und die rohen Datenbank-Ordner:
# /etc/restic/excludes
/srv/appdata/*/postgres
/srv/appdata/immich/data/thumbs
/srv/appdata/immich/data/encoded-video
*.sqlite3-wal
*.sqlite3-shmUnd dann der eigentliche Befehl:
sudo restic -r sftp:storagebox:restic --password-file /etc/restic/password \
backup --one-file-system --exclude-caches --exclude-file=/etc/restic/excludes \
--tag daily /srv/backup-dumps /srv/appdata /srv/dataDer erste Lauf hat bei mir mehrere Stunden gedauert, weil alle Fotos einmal hochgeladen werden mussten. Seitdem dauert ein Lauf nur noch ein paar Minuten.
Alte Snapshots aufräumen
Sonst wächst das Repository ja endlos. restic kann alte Stände nach Regeln löschen. Ich behalte die letzten 7 Tage, 4 Wochen und 12 Monate:
sudo restic -r sftp:storagebox:restic --password-file /etc/restic/password \
forget --prune --keep-daily 7 --keep-weekly 4 --keep-monthly 12Prüfen, ob die Daten noch lesbar sind
Einmal pro Woche lässt mein Skript restic das Repository prüfen und dabei zufällig 5 % der Daten tatsächlich herunterladen und entschlüsseln. So fällt es auf, wenn auf dem Speicher etwas kaputtgeht, ohne dass jedes Mal das ganze Backup gelesen werden muss:
sudo restic -r sftp:storagebox:restic --password-file /etc/restic/password \
check --read-data-subset=5%Automatisch jede Nacht mit systemd
Das alles steckt in einem Skript, das ein systemd-Timer jede Nacht um 3:30 Uhr startet. Ich mag systemd-Timer lieber als Cronjobs, weil man die Logs direkt mit journalctl sieht und Persistent=true einen verpassten Lauf nachholt, falls der Server um 3:30 Uhr aus war:
# /etc/systemd/system/homelab-backup.timer
[Unit]
Description=Tägliches Homelab-Backup
[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=15min
Persistent=true
[Install]
WantedBy=timers.targetAktivieren und den nächsten Termin anzeigen:
sudo systemctl enable --now homelab-backup.timer
systemctl list-timers homelab-backup.timerZwischendurch von Hand starten und zuschauen geht so:
sudo systemctl start --no-block homelab-backup
journalctl -fu homelab-backupDas Skript sichert nacheinander in beide Ziele. Fällt eins aus, zum Beispiel weil das NAS gerade aus ist, wird das andere trotzdem gesichert und der Fehler gemeldet.
Woher weiß ich, dass es funktioniert?
Ein Backup, von dem man nicht weiß, ob es läuft, ist fast so schlimm wie gar keins. Deshalb gibt es drei Wege, wie ich davon erfahre:
1. Uptime Kuma (Push-Monitor). Am Ende jedes Laufs schickt das Skript ein kurzes „alles gut“ an Uptime Kuma. Bleibt die Meldung länger als einen Tag aus, bekomme ich eine Push-Nachricht aufs Handy:
curl -fsS -m 10 -G "http://127.0.0.1:3001/api/push/${BACKUP_KUMA_PUSH_TOKEN}" \
--data-urlencode "status=up" --data-urlencode "msg=OK"2. Prometheus und Grafana. Das Skript schreibt ein paar Werte als Textdatei für den node-exporter, zum Beispiel wann das letzte erfolgreiche Backup war. Daraus gibt es einen Alarm, wenn das letzte Backup älter als 26 Stunden ist:
homelab_backup_last_success_timestamp_seconds 1792200614
homelab_backup_last_result 13. Ein Dashboard mit Fortschrittsbalken. restic kann seinen Fortschritt mit --json ausgeben. Den lese ich mit einem kleinen Skript mit und zeige ihn in Grafana an: welches Ziel gerade dran ist, wie viel schon geschafft ist und wie lange es noch dauert. Dazu gibt es einen „Backup jetzt“-Knopf, der hinter dem Login liegt. Gebraucht hätte ich das nicht, aber es war ein cooles Mini-Projekt, und ich sehe auf einen Blick, ob alles läuft.
Der wichtigste Schritt: Wiederherstellen üben
Ein Backup ist erst dann ein Backup, wenn man es auch zurückspielen kann. Deshalb habe ich einmal bewusst einen Dienst (Uptime Kuma) komplett gelöscht und aus dem Backup wiederhergestellt.
Zuerst schauen, welche Snapshots es gibt:
sudo restic -r sftp:storagebox:restic --password-file /etc/restic/password snapshotsDann nur die benötigten Ordner aus dem neuesten Snapshot in ein temporäres Verzeichnis holen:
sudo restic -r sftp:storagebox:restic --password-file /etc/restic/password \
restore latest --target /tmp/restore \
--include /srv/appdata/uptime-kuma \
--include /srv/backup-dumps/uptime-kuma.sqlite3Danach die Ordner an ihren Platz kopieren, die gesicherte Datenbank-Kopie als kuma.db darüberlegen und den Container wieder starten. Kurz darauf war alles wieder da, inklusive Verlauf.
Für eine Datenbank aus PostgreSQL geht das Einspielen ähnlich, nur eben mit dem Dump:
gunzip -c /srv/backup-dumps/paperless.sql.gz \
| docker exec -i paperless-db psql -U paperless -d paperlessEin praktischer Trick zum Stöbern: restic mount hängt alle Snapshots als normales Verzeichnis ein. Dann kann man sich mit ls und cp einzelne Dateien von einem bestimmten Tag herausholen:
sudo mkdir -p /mnt/restic
sudo restic -r /mnt/backup/restic/docker-vm --password-file /etc/restic/password mount /mnt/resticIch habe mir vorgenommen, so einen Test einmal im Jahr zu wiederholen, und es steht als wiederkehrende Aufgabe in meiner Roadmap.
Fazit
Das Einrichten hat länger gedauert, als ich gedacht hätte, vor allem die Sache mit den Datenbanken und das Monitoring drumherum. Aber jetzt habe ich zum ersten Mal das Gefühl, dass meine Daten wirklich sicher sind und nicht nur „irgendwo noch eine Kopie liegt“.
Was als Nächstes kommt: Im November ersetzt ein neues NAS mit TrueNAS das alte ReadyNAS. Dann wandert die lokale Kopie dorthin, und ich will mir anschauen, ob ich zusätzlich ZFS-Snapshots nutze.
Falls ihr selbst noch keine Backups für euer Homelab habt: Fangt einfach mit einem Ziel an. Ein restic-Repository auf einer externen Festplatte ist in zehn Minuten eingerichtet und viel besser als nichts.
Zum Weiterlesen
Wer tiefer einsteigen will, findet hier die Quellen, die mir beim Einrichten am meisten geholfen haben:
- restic-Dokumentation – sehr gut geschrieben, vor allem die Kapitel zu Repositories und Wiederherstellen
- Snapshots nach Regeln entfernen – wie
forgetmit--keep-daily& Co. genau rechnet - Hetzner-Tutorial: Storage Box mit restic und Docker – ein anderer Aufbau mit Docker, gut zum Vergleichen
- restic mit einer Hetzner Storage Box – älterer, aber ausführlicher Erfahrungsbericht zum SFTP-Weg
- SQLite Online Backup – warum man laufende SQLite-Datenbanken nicht einfach kopieren sollte
- pg_dump – alle Optionen für PostgreSQL-Dumps, z. B.
--cleanund--if-exists - systemd-Timer im Arch Wiki – die beste Erklärung zu
OnCalendar,Persistentund Co., auch wenn man kein Arch nutzt