PostgreSQL Backup-Strategie: Automatische Backups einrichten

Learn how to set up automatic PostgreSQL backups with pg_dump, cron jobs, and rotation to keep your VPS data safe.

PostgreSQL Backup-Strategie: Automatische Backups einrichten

Backups sind eines der Themen, bei denen die Lücke zwischen "machen wir mal" und "funktioniert tatsächlich" besonders groß ist. Ich habe in den letzten Jahren mit zwei Datenverlust-Szenarien zu tun gehabt — einmal eigene Datenbank, einmal die eines Freundes. In beiden Fällen gab es nominell Backups. In beiden Fällen waren sie nicht wiederherstellbar.

Daraus habe ich gelernt, dass eine Backup-Strategie aus zwei Teilen besteht: dem Backup selbst und dem regelmäßigen Restore-Test. Den zweiten Teil überspringen die meisten.

Das absolute Minimum

pg_dump für eine einzelne Datenbank, in eine Datei mit Datum:

#!/bin/bash
set -euo pipefail

BACKUP_DIR=/var/backups/postgres
mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d-%H%M%S)

pg_dump -Fc -U postgres app > "$BACKUP_DIR/app-$DATE.dump"

-Fc ist das Custom-Format, komprimiert und mit pg_restore selektiv wiederherstellbar. Plain SQL (-Fp) lässt sich mit weniger Tools öffnen, ist aber bei jedem Restore eine 1:1-Replay-Aktion — keine Möglichkeit, einzelne Tabellen rauszupicken.

Für mehrere Datenbanken auf einem Cluster: pg_dumpall für die globalen Objekte (Users, Roles), dann pro Datenbank ein einzelner pg_dump. Wer alles in einen großen Dump steckt, verliert die Granularität.

Rotation, damit der Server nicht volläuft

Naive Rotation ist eine Fehlerquelle. Ich habe einmal ein Backup-Script gehabt, das find -mtime +30 -delete nutzte — funktionierte, bis Cron einen Tag ausfiel. Beim nächsten Lauf waren genau die Backups gelöscht, die ich gebraucht hätte.

Mein aktuelles Schema folgt grob der Großvater-Vater-Sohn-Logik:

  • 7 tägliche Backups
  • 4 wöchentliche
  • 6 monatliche

Implementiert mit benannten Verzeichnissen statt mtime:

DAY=$(date +%u)
WEEK=$(date +%V)
MONTH=$(date +%m)

pg_dump -Fc -U postgres app > "$BACKUP_DIR/daily-$DAY.dump"

if [[ $(date +%u) -eq 7 ]]; then
  cp "$BACKUP_DIR/daily-$DAY.dump" "$BACKUP_DIR/weekly-$WEEK.dump"
fi

if [[ $(date +%d) -eq 01 ]]; then
  cp "$BACKUP_DIR/daily-$DAY.dump" "$BACKUP_DIR/monthly-$MONTH.dump"
fi

Das Schöne daran: alte Backups werden überschrieben statt gelöscht. Nichts wird "vergessen", nichts läuft voll.

Off-Site, sonst sind's keine Backups

Ein Backup auf demselben Server wie die Datenbank ist nicht wirklich ein Backup. Wenn der Server abraucht, ist beides weg. Ich kopiere meine Dumps zusätzlich per rsync auf einen zweiten VPS und in einen S3-kompatiblen Storage:

rsync -az "$BACKUP_DIR/" backup-server:/var/backups/db/

# Hetzner Storage Box, aber jeder S3-Provider geht
rclone sync "$BACKUP_DIR" remote:db-backups/

rclone mit Verschlüsselung dazwischen, damit der Storage-Provider die Inhalte nicht sieht.

Cron, der nicht still scheitert

Klassischer Cron-Eintrag für 3 Uhr morgens:

0 3 * * * /usr/local/bin/pg-backup.sh 2>&1 | logger -t pg-backup

logger -t pg-backup schreibt den Output in syslog mit einem Tag. Dadurch landet er im Standard-Log und kann von Logwatch o.ä. picked up werden.

Das Wichtige: kein > /dev/null. Wenn was schiefgeht, will ich es sehen. Ich pipe die Ausgabe zusätzlich an einen mailx-Trigger, der mich nur bei Exit-Code != 0 anpingt.

Restore testen, regelmäßig

Das hier ist der Teil, der bei Anleitungen meist fehlt. Backups sind nicht real, bis sie restored wurden.

Mein Test-Script:

#!/bin/bash
set -euo pipefail

LATEST=$(ls -t /var/backups/postgres/daily-*.dump | head -1)
TEST_DB="restore_test_$(date +%s)"

createdb -U postgres "$TEST_DB"
pg_restore -U postgres -d "$TEST_DB" "$LATEST"

ROW_COUNT=$(psql -U postgres -d "$TEST_DB" -tAc "SELECT count(*) FROM users")
dropdb -U postgres "$TEST_DB"

if [[ $ROW_COUNT -lt 1 ]]; then
  echo "WARNUNG: Restore enthält keine User"
  exit 1
fi

Läuft bei mir wöchentlich, mailt mir Erfolg oder Fehler. Hat einmal ein Problem gefunden — eine pg_dump-Version-Mismatch, die im normalen Backup-Lauf still einen warning-only-Dump produzierte, der beim Restore aber inkonsistent war.

Point-in-Time Recovery — wenn's wirklich wichtig ist

Für Production-Datenbanken mit Compliance-Anforderungen reicht pg_dump nicht. Da brauchst du WAL-Archivierung mit pgBackRest oder Barman. Damit kannst du auf jeden beliebigen Zeitpunkt zurück, nicht nur auf den nächsten Backup-Schnappschuss.

Für Hobby-Projekte und kleinere Apps ist das overkill. Tägliche Dumps mit Restore-Test reichen — solange du den Restore-Test wirklich machst.