und automatisierte Restore-Tests
Ein Backup, das nie getestet wurde, ist eine Hypothese. Bash-Backup-Skripte, die rsync für inkrementelle Sicherungen und tar für archivierte Snapshots kombinieren, rotierende Sicherungen nach konfigurierbarer Retention aufräumen und Restore-Tests automatisch ausführen, machen den Unterschied zwischen einer Datensicherungs-Routine und einem verifizierten Wiederherstellungsprozess.
Inhaltsverzeichnis
- 1. Backup-Strategie: was ein gutes Bash-Backup-Skript leistet
- 2. rsync für inkrementelle Sicherungen
- 3. tar-Snapshots mit Zeitstempel und Kompression
- 4. Rotierende Sicherungen: Retention und Aufräumen
- 5. Datenbank-Backups mit Bash orchestrieren
- 6. Backup-Verifikation: Prüfsummen und Integritätstests
- 7. Automatisierte Restore-Tests
- 8. Benachrichtigung und Monitoring
- 9. Backup-Strategien im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Backup-Strategie: was ein gutes Bash-Backup-Skript leistet
Ein professionelles Bash-Backup-Skript löst vier Kernprobleme: Es erstellt zuverlässig Sicherungen ohne stille Fehler, rotiert alte Sicherungen nach konfigurierbarer Retention, verifiziert die Integrität der erstellten Backups und testet regelmäßig, ob ein Restore tatsächlich funktioniert. Die meisten einfachen Backup-Skripte lösen nur das erste Problem – und scheitern erst in dem Moment, wenn ein Restore wirklich gebraucht wird.
Das Fundament jedes Bash-Backup-Skripts ist set -euo pipefail kombiniert mit einem trap cleanup EXIT-Handler. Wenn der Backup-Prozess mitten im Lauf abbricht – wegen vollem Dateisystem, Netzwerkfehler oder Signal – darf kein unvollständiges Backup als vollständig im Backup-Verzeichnis erscheinen. Das Muster: Backups in ein temporäres Verzeichnis schreiben und erst nach erfolgreichem Abschluss umbenennen. So ist jede Datei im Backup-Zielverzeichnis entweder vollständig oder gar nicht vorhanden.
Die 3-2-1-Backup-Regel lässt sich mit Bash-Backup-Skripten gut umsetzen: drei Kopien, auf zwei verschiedenen Medien, eine davon off-site. Das Skript erstellt das lokale Backup mit rsync oder tar, überträgt es auf einen Remote-Host via rsync über SSH und meldet den Status aller drei Schritte. Jeder Schritt hat seinen eigenen Exit-Code-Check und Logging-Eintrag. Ein fehlendes Off-site-Backup ist ein bekannter, eskalierter Fehler – kein stiller Ausfall.
2. rsync für inkrementelle Sicherungen
rsync ist das optimale Werkzeug für inkrementelle Bash-Backups: es überträgt nur geänderte Daten, bewahrt Berechtigungen und Symlinks, unterstützt SSH als Transport und hat eine umfangreiche Filterkonfiguration für ausgeschlossene Verzeichnisse. Das Kernmuster für ein inkrementelles rsync-Backup mit Hard-Links ist rsync --link-dest: das vorherige Backup dient als Referenz, unveränderte Dateien werden als Hard-Links eingebunden statt kopiert. Jeder Snapshot sieht vollständig aus, verbraucht aber nur den Speicher für tatsächlich geänderte Dateien.
Die wichtigsten rsync-Flags für Bash-Backup-Skripte: -a (Archive-Modus: rekursiv, Symlinks, Berechtigungen, Zeitstempel, Owner, Gruppe), --delete (gelöschte Dateien in der Quelle auch im Ziel entfernen), --exclude-from (Ausschlussliste aus Datei lesen, für große Ausschlusslisten wartbarer als Inline-Flags), --stats (Transferstatistiken ins Log schreiben), --checksum (statt Zeitstempel-Vergleich echte Checksummen für maximale Integrität) und --bwlimit (Bandbreitenlimit, um Produktionssysteme nicht zu überlasten). Die Kombination aus diesen Flags in einem Konfigurationsfile macht das Backup-Skript ohne Codeänderung anpassbar.
#!/usr/bin/env bash
# rsync_backup.sh — incremental backup with hard-link snapshots
set -euo pipefail
IFS=$'\n\t'
BACKUP_ROOT="${BACKUP_ROOT:-/backups}"
SOURCE_DIRS=("${@:-/var/www /etc /home}")
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
LATEST_LINK="$BACKUP_ROOT/latest"
CURRENT_BACKUP="$BACKUP_ROOT/snapshot-$TIMESTAMP"
INCOMPLETE="$BACKUP_ROOT/incomplete-$TIMESTAMP"
EXCLUDES=(
"*.tmp" "*.swp" ".git/objects"
"var/cache" "var/log" "node_modules"
".Trash*" "lost+found"
)
# Build rsync exclude arguments
rsync_exclude_args=()
for pattern in "${EXCLUDES[@]}"; do
rsync_exclude_args+=(--exclude="$pattern")
done
cleanup() {
# Remove incomplete backup directory on failure
[[ -d "$INCOMPLETE" ]] && rm -rf "$INCOMPLETE"
}
trap cleanup EXIT
# Write to incomplete dir first — rename only on success
mkdir -p "$INCOMPLETE"
# Use previous snapshot as hard-link reference if it exists
link_dest_args=()
[[ -d "$LATEST_LINK" ]] && link_dest_args=(--link-dest="$LATEST_LINK")
rsync -aH --delete --stats --numeric-ids \
"${rsync_exclude_args[@]}" \
"${link_dest_args[@]}" \
"${SOURCE_DIRS[@]}" \
"$INCOMPLETE/"
# Atomic rename: only now is the backup "complete"
mv "$INCOMPLETE" "$CURRENT_BACKUP"
# Update latest symlink atomically
ln -sfn "$CURRENT_BACKUP" "${LATEST_LINK}.new"
mv -Tf "${LATEST_LINK}.new" "$LATEST_LINK"
echo "[OK] Backup completed: $CURRENT_BACKUP"
du -sh "$CURRENT_BACKUP"
3. tar-Snapshots mit Zeitstempel und Kompression
tar-Archive sind die klassische Methode für Bash-Backups mit vollständigen Snapshots: ein einzelnes Archiv pro Backup-Lauf, komprimiert, mit Prüfsumme verifizierbar und einfach zu transportieren. Das Standard-Muster für ein tar-Backup-Skript: Dateiname mit ISO-Zeitstempel, Kompression mit gzip, bzip2 oder xz je nach Anforderung an Kompressionsrate vs. Geschwindigkeit, anschließend SHA256-Prüfsumme berechnen und in einer Begleitdatei ablegen.
Ein kritischer Aspekt bei tar-Backup-Skripten in Bash: tar gibt bei bestimmten Warnungen (Changed files, Cannot open) Exit-Code 1 zurück, nicht 2. Mit set -e würde das Skript bei diesen harmlosen Warnungen abbrechen. Das korrekte Muster: tar_exit=0; tar czf archiv.tgz /quelle || tar_exit=$?; (( tar_exit > 1 )) && { echo "tar fatal error"; exit 1; }. Exit-Code 1 bedeutet bei tar "Dateien verändert während des Backups" – das ist für inkrementelle Live-Backups akzeptabel. Exit-Code 2 bedeutet einen fatalen Fehler und muss zu einem Skript-Abbruch führen.
4. Rotierende Sicherungen: Retention und Aufräumen
Rotierende Bash-Backups implementieren eine Aufbewahrungsrichtlinie, die Speicherplatz kontrolliert und trotzdem ausreichend History bewahrt. Das einfachste Rotationsmuster: alle Backups älter als N Tage löschen. find "$BACKUP_ROOT" -maxdepth 1 -name "snapshot-*" -mtime +7 -exec rm -rf {} \; löscht alle Snapshot-Verzeichnisse, die älter als 7 Tage sind. Das ist einfach, aber nicht granular.
Ein ausgefeilteres Rotationsmuster in Bash folgt dem Großvater-Vater-Sohn-Prinzip: tägliche Backups für die letzten 7 Tage, wöchentliche für die letzten 4 Wochen, monatliche für die letzten 12 Monate. In Bash umsetzbar durch Namenskonventionen: täglich mit daily-YYYYMMDD, wöchentlich mit weekly-YYYY-Wnn, monatlich mit monthly-YYYY-MM. Das Rotations-Skript prüft welche wöchentlichen/monatlichen Backups noch fehlen, kopiert das beste verfügbare tägliche Backup in die entsprechende Kategorie, und löscht tägliche Backups außerhalb des Retention-Fensters. Dieses Muster ergibt maximal 7+4+12=23 gespeicherte Backups statt einer unkontrollierten Anhäufung.
#!/usr/bin/env bash
# backup_rotation.sh — grandfather-father-son rotation for Bash backups
set -euo pipefail
BACKUP_ROOT="${BACKUP_ROOT:-/backups}"
DAILY_RETAIN=7
WEEKLY_RETAIN=4
MONTHLY_RETAIN=12
# Sort backups by date, keep only the newest N of each category
rotate_category() {
local pattern="$1" keep="$2"
local -a all=()
mapfile -t all < <(find "$BACKUP_ROOT" -maxdepth 1 -name "$pattern" -type d | sort -r)
local count=${#all[@]}
if (( count > keep )); then
local to_delete=("${all[@]:$keep}")
for old in "${to_delete[@]}"; do
echo "[ROTATE] Removing old backup: $old"
rm -rf "$old"
done
echo "[OK] Rotated $pattern: kept $keep, deleted $((count - keep))"
else
echo "[OK] $pattern: $count backups (within limit of $keep)"
fi
}
# Copy latest daily to weekly/monthly if not yet present
promote_to_weekly() {
local week_stamp
week_stamp=$(date +%Y-W%V)
local weekly_dir="$BACKUP_ROOT/weekly-$week_stamp"
local latest="$BACKUP_ROOT/latest"
if [[ ! -d "$weekly_dir" ]] && [[ -L "$latest" ]]; then
echo "[INFO] Creating weekly backup: $weekly_dir"
cp -al "$latest" "$weekly_dir"
fi
}
promote_to_monthly() {
local month_stamp
month_stamp=$(date +%Y-%m)
local monthly_dir="$BACKUP_ROOT/monthly-$month_stamp"
local latest="$BACKUP_ROOT/latest"
if [[ ! -d "$monthly_dir" ]] && [[ -L "$latest" ]]; then
echo "[INFO] Creating monthly backup: $monthly_dir"
cp -al "$latest" "$monthly_dir"
fi
}
promote_to_weekly
promote_to_monthly
rotate_category "snapshot-*" "$DAILY_RETAIN"
rotate_category "weekly-*" "$WEEKLY_RETAIN"
rotate_category "monthly-*" "$MONTHLY_RETAIN"
# Report remaining backup count and total size
total_size=$(du -sh "$BACKUP_ROOT" 2>/dev/null | cut -f1)
echo "[REPORT] Total backup size: $total_size"
5. Datenbank-Backups mit Bash orchestrieren
Datenbank-Backups in Bash erfordern besondere Aufmerksamkeit, weil eine Datenbank ein laufender Zustand ist, der ohne Transaktions-Isolation inkonsistent gesichert werden kann. Bei MySQL/MariaDB ist mysqldump --single-transaction für InnoDB-Tabellen der Schlüssel zu konsistenten Snapshots ohne exklusive Sperren. Für PostgreSQL ist pg_dump -Fc (Custom Format) das empfohlene Format, weil es parallele Restores mit pg_restore -j ermöglicht und komprimiert ist. Das Bash-Backup-Skript orchestriert den Dump, die Kompression, die Prüfsumme und die Übertragung in eine einzige, atomare Operation.
Ein oft übersehener Aspekt von Datenbank-Backup-Skripten in Bash: der Unterschied zwischen einem logischen Dump und einem physischen Snapshot. Logische Dumps (mysqldump) sind langsamer, aber portabler und leichter zu verifizieren. Physische Snapshots über LVM oder ZFS sind schneller und ermöglichen Point-in-Time-Recovery, erfordern aber identische Datenbankversion beim Restore. Das Bash-Backup-Skript sollte explizit dokumentieren, welche Strategie verwendet wird, und den Dump mit Metadaten versehen: Datenbankversion, Zeitpunkt, Größe und Zeilenanzahl der kritischsten Tabellen.
6. Backup-Verifikation: Prüfsummen und Integritätstests
Backup-Verifikation in einem Bash-Backup-Skript geht über das bloße Prüfen des Exit-Codes hinaus. Eine erstellte tar-Datei kann einen korrekten Exit-Code liefern und trotzdem beschädigt sein – wegen Festplattenfehler, Netzwerkproblemen oder Speicherplatzmangel, der erst nach dem letzten Schreibzugriff auftrat. Das Standardmuster: SHA256-Prüfsumme unmittelbar nach der Erstellung berechnen und in einer Begleitdatei ablegen. Vor jedem Restore und in einem separaten Verifikationsjob die Prüfsumme gegen die gespeicherte prüfen.
Für rsync-basierte Backups in Bash ist die Verifikation anders: statt einer Prüfsumme über ein Archiv prüft man, ob die Dateianzahl und Gesamtgröße plausibel sind. Ein Backup, das plötzlich 90% weniger Dateien enthält als das vorherige, ist ein Signal für einen Fehler – egal ob der Exit-Code 0 war. Das Bash-Backup-Skript sollte nach jedem rsync-Lauf die Dateianzahl im neuen Backup gegen das vorherige prüfen und bei einer Abweichung von mehr als einem konfigurierbaren Schwellwert einen Fehler ausgeben und eskalieren.
#!/usr/bin/env bash
# backup_verify.sh — checksum and sanity verification for Bash backups
set -euo pipefail
BACKUP_ROOT="${BACKUP_ROOT:-/backups}"
VARIANCE_THRESHOLD=20 # alert if file count drops more than 20%
verify_archive() {
local archive="$1"
local checksum_file="${archive}.sha256"
if [[ ! -f "$checksum_file" ]]; then
echo "[ERROR] No checksum file for: $archive" >&2; return 1
fi
echo "[INFO] Verifying checksum: $archive"
if sha256sum --check "$checksum_file" --quiet; then
echo "[OK] Checksum verified: $archive"
else
echo "[ERROR] Checksum MISMATCH: $archive — backup may be corrupted" >&2
return 1
fi
# Test tar integrity without extracting (--list reads the table of contents)
echo "[INFO] Testing archive integrity: $archive"
if tar --list --file="$archive" >/dev/null 2>&1; then
local file_count
file_count=$(tar --list --file="$archive" 2>/dev/null | wc -l)
echo "[OK] Archive readable: $file_count entries"
else
echo "[ERROR] Archive is corrupted or truncated: $archive" >&2
return 1
fi
}
verify_rsync_snapshot() {
local current="$1" previous="$2"
[[ ! -d "$current" ]] && { echo "[ERROR] Backup dir not found: $current" >&2; return 1; }
[[ ! -d "$previous" ]] && { echo "[WARN] No previous backup for comparison"; return 0; }
local current_count previous_count
current_count=$(find "$current" -type f | wc -l)
previous_count=$(find "$previous" -type f | wc -l)
local drop_pct=0
(( previous_count > 0 )) && drop_pct=$(( (previous_count - current_count) * 100 / previous_count ))
if (( drop_pct > VARIANCE_THRESHOLD )); then
echo "[ERROR] File count dropped $drop_pct% (prev=$previous_count, curr=$current_count)" >&2
return 1
fi
echo "[OK] File count OK: $current_count files (${drop_pct}% change from $previous_count)"
}
# Run verification on the latest backup
latest="$BACKUP_ROOT/latest"
[[ -L "$latest" ]] || { echo "[ERROR] No 'latest' symlink found" >&2; exit 1; }
resolved=$(readlink -f "$latest")
# Find the backup before the latest for comparison
prev_backup=$(find "$BACKUP_ROOT" -maxdepth 1 -name "snapshot-*" -type d | sort | tail -2 | head -1)
verify_rsync_snapshot "$resolved" "$prev_backup"
echo "[DONE] Backup verification completed for: $resolved"
7. Automatisierte Restore-Tests
Automatisierte Restore-Tests in Bash sind die einzige Möglichkeit zu wissen, dass ein Backup tatsächlich wiederhergestellt werden kann. Ein Backup ohne Restore-Test ist eine ungeprüfte Hypothese. Das Muster für einen automatisierten Restore-Test im Bash-Backup-Skript: periodisch (wöchentlich oder monatlich) das aktuellste Backup in eine isolierte Testumgebung wiederherstellen, eine Reihe von Smoke-Tests ausführen und das Ergebnis protokollieren. "Isolierte Testumgebung" kann ein Docker-Container, eine temporäre Verzeichnis-Struktur oder eine VM-Snapshot sein.
Für Datenbank-Restore-Tests in Bash ist das Muster: Dump in eine temporäre Datenbank importieren, kritische Tabellen auf Zeilenanzahl und Konsistenz prüfen, anschließend die temporäre Datenbank löschen. Das gesamte Bash-Skript für den Restore-Test läuft automatisch nach jedem Backup-Lauf oder als separater Cron-Job. Bei Fehlern wird eskaliert. Bei Erfolg wird das Ergebnis mit Zeitstempel in einem Restore-Test-Log festgehalten. Dieses Log ist der Nachweis, dass das Backup-System funktioniert – nicht die Backup-Dateien selbst.
8. Benachrichtigung und Monitoring
Ein Bash-Backup-Skript ohne Benachrichtigung ist ein Skript, das still versagen kann. Das Monitoring-Muster: bei Erfolg eine Success-Meldung mit Metadaten (Backup-Größe, Dauer, Dateianzahl) an ein zentrales Monitoring-System senden – bei Fehler sofort einen Alert auslösen. Für einfache Benachrichtigungen genügt ein curl-Aufruf an einen Slack-Webhook oder eine E-Mail über sendmail. Für strukturiertes Monitoring kann ein Heartbeat-Dienst wie Healthchecks.io verwendet werden: bei erfolgreichem Backup ein HTTP-GET senden, und wenn der Heartbeat ausbleibt, löst der Dienst einen Alert aus.
Das überlegene Muster für Backup-Monitoring in Bash: nicht nur Fehler melden, sondern auch Erfolg aktiv bestätigen. Ein Backup-Skript, das bei Erfolg schweigt und nur bei Fehler schreibt, gibt keine Garantie, dass es überhaupt läuft. Wenn der Cron-Job aus irgendeinem Grund nicht ausgelöst wird – Systemabsturz, Cron-Konfigurationsfehler, volle Crontab-Mail-Queue – bemerkt man das ohne Success-Heartbeat erst, wenn man das Backup tatsächlich braucht. Ein aktiver Heartbeat an ein externes System ist die sicherste Implementierung.
9. Backup-Strategien im Vergleich
Die Wahl der Bash-Backup-Strategie hängt von den Anforderungen an Recovery Time Objective (RTO) und Recovery Point Objective (RPO) ab.
| Strategie | Speicherbedarf | Restore-Zeit | Empfehlung |
|---|---|---|---|
| rsync --link-dest | Niedrig (Hard-Links) | Schnell (fertige Files) | Tägliche inkrementelle File-Backups |
| tar + gzip/xz | Mittel (komprimiert) | Mittel (Extraktion nötig) | Wöchentliche/monatliche Snapshots |
| mysqldump | Mittel (SQL-Text) | Langsam (SQL-Import) | DB-Backups, gute Portabilität |
| LVM-Snapshot | Sehr niedrig (CoW) | Sehr schnell | Große Datenmengen, point-in-time |
| GFI-Rotation | Kontrolliert (23 max) | Je nach Typ | Produktion mit langer History |
In der Praxis kombiniert ein robustes Bash-Backup-System mehrere Strategien: rsync für tägliche inkrementelle Datei-Backups, mysqldump für tägliche Datenbank-Dumps, tar für wöchentliche vollständige Snapshots und Großvater-Vater-Sohn-Rotation über alle Kategorien. Das Bash-Skript orchestriert alle drei, führt Verifikation durch und testet monatlich automatisch den Restore.
Mironsoft
Backup-Automatisierung, Shell-Scripting und Deployment-Infrastruktur
Backup-System mit verifizierten Restore-Tests?
Wir entwickeln Bash-Backup-Skripte mit rsync-Inkrementierung, tar-Rotation, Prüfsummen-Verifikation und automatisierten Restore-Tests – damit euer Backup nicht erst beim Notfall auf Funktionsfähigkeit geprüft wird.
Backup-Design
rsync, tar und DB-Dumps mit GFI-Rotation und Retention-Richtlinien
Verifikation
Prüfsummen, Dateianzahl-Plausibilität und automatisierte Restore-Tests
Monitoring
Heartbeat-Integration, Alert-Eskalation und Backup-Status-Dashboard
10. Zusammenfassung
Bash für Backups, rotierende Sicherungen und Restore-Tests bedeutet: eine Backup-Routine bauen, die über das bloße Erstellen von Archiven hinausgeht. rsync mit --link-dest für speichereffiziente inkrementelle Snapshots. Atomare Backups durch Schreiben in temporäre Verzeichnisse und Umbenennen erst nach erfolgreichem Abschluss. Großvater-Vater-Sohn-Rotation für kontrollierte Aufbewahrung. SHA256-Prüfsummen und Dateianzahl-Plausibilitätsprüfungen für Verifikation. Automatisierte Restore-Tests in isolierten Umgebungen als einziger Nachweis, dass ein Backup tatsächlich funktioniert.
Die kritischste Erkenntnis: ein Bash-Backup-Skript ohne Restore-Test ist kein Backup-System, sondern ein Archivierungsprozess mit ungeprüfter Wiederherstellbarkeit. Der Aufwand für einen automatisierten Restore-Test – selbst wenn er nur eine Dateianzahl-Prüfung und einen mysqldump-Import in eine Testdatenbank umfasst – ist der wichtigste Schritt vom "Backup vorhanden" zum "Backup verifiziert".
Bash für Backups — Das Wichtigste auf einen Blick
Atomare Backups
In temp. Verzeichnis schreiben, erst nach Erfolg umbenennen. So gibt es nur vollständige oder gar keine Backups. trap cleanup EXIT entfernt incomplete-Verzeichnisse.
rsync Inkrementell
--link-dest für Hard-Link-Snapshots: jedes Backup sieht vollständig aus, verbraucht aber nur Speicher für geänderte Dateien. --delete löscht entfernte Dateien im Backup.
GFI-Rotation
7 tägliche + 4 wöchentliche + 12 monatliche = max. 23 Backups. cp -al für Hard-Link-Kopien bei Promotion zu weekly/monthly – kein extra Speicherplatz.
Restore-Tests
Automatisiert: Dump in Testdatenbank importieren, Zeilenanzahl prüfen, löschen. Das einzige Mittel, das beweist, dass ein Backup tatsächlich wiederhergestellt werden kann.