⚡ HomelabVergleich

Datenbank-Backups richtig planen: PostgreSQL, MySQL und die Container-Falle

Einleitung: Warum Dateisystem-Backups allein nicht reichen

Ein Backup, das nie getestet wurde, ist kein Backup. Diese Erkenntnis ist besonders schmerzhaft, wenn sie im Moment des Datenverlusts aufkommt – etwa wenn Nextcloud aus dem Nothingness zurückkehrt, Vaultwarden Passwörter verweigert oder Immich nicht mehr auf alte Fotos zugreifen kann. Die meisten Homelab-Betreiber sichern die Datenverzeichnisse ihrer Container oder VMs mit rsync, Borg oder Restic – und glauben, sie wären gesichert. Doch wenn die Datenbank im aktuellen Zustand schreibt, während das Backup läuft, bleibt das Ergebnis hinter dem eigentlichen Datenbestand zurück.

In diesem Artikel geht es nicht um Backup-Produkte oder empfohlene Software. Es geht um das Verständnis dessen, was bei PostgreSQL und MySQL passiert, wenn man den Datenbankspeicher einfach kopiert – und wie man es richtig macht.

Warum das Kopieren des Datenverzeichnisses nicht funktioniert

Sowohl PostgreSQL als auch MySQL schreiben ihre Daten nicht einfach sequenziell in Dateien, sondern nutzen transaktionsbasierte Mechanismen, die während eines laufenden Backups zu inkonsistenten Zuständen führen können.

PostgreSQL: WAL, Shared Buffers und inkonsistente Seiten

PostgreSQL schreibt Änderungen zunächst in ein Write-Ahead Log (WAL) und hält Änderungen im Shared-Buffer-Cache, bevor sie auf die Plattenfläche fallen. Das Datenverzeichnis unter /var/lib/postgresql/data (oder einem custom data_directory) enthält die Datenbankdateien – aber diese Dateien können zu jedem Moment teilweise geschrieben sein. Ein einfaches cp -r oder rsync des Verzeichnisses erfasst nicht:

Ein Copy des Datenverzeichnisses kann auch zu korrupten Backups führen, wenn PostgreSQL gerade eine Tabelle oder einen Index reorganisiert (autovacuum, REINDEX) und dabei Dateisperren oder Dateien mit inkonsistenten Größen auftreten.

MySQL/MariaDB: InnoDB, Redo-Logs und MyISAM-Schreibsperren

MySQL mit InnoDB speichert Daten in Tabellenräumen (.ibd-Dateien) und nutzt Redo-Logs (ib_logfile0, ib_logfile1), um Crash-Recovery zu ermöglichen. Wenn man während des Betriebs das Datenverzeichnis kopiert, kann man InnoDB-Tabellendaten in einem Zustand erhalten, der keine konsistente Wiederherstellung ohne die entsprechenden Redo-Log-Einträge erlaubt. Zudem sind MyISAM-Tabellen (falls noch genutzt) nur durch flüchtige .frm- und .MYD-/.MYI-Dateien geschützt – ohne ausreichende Sperren können diese bei einem Dateikopier-Vorgang beschädigt werden.

Dateirechte und Besitz

Ein weiteres Problem: Datenbank-Verzeichnisse gehören typischerweise dem Datenbank-Benutzer (z.B. postgres oder mysql). Ein ungeplanter Kopiervorgang mit root-Rechten auf ein anderes Ziel kann diese Besitzverhältnisse durcheinanderbringen und die Datenbank beim Wiederherstellungsvorgang daran hindern, die Dateien zu öffnen.

Der korrekte Weg für PostgreSQL: logisches Backup mit pg_dump

PostgreSQL bietet das Werkzeug pg_dump für logische Backups. Ein logisches Backup bedeutet, dass die Datenbankinhalte als SQL-Befehle oder im eigenen Compressed-Format exportiert werden – unabhängig von der physischen Speicherstruktur.

Grundform


pg_dump -U postgres -d meine_datenbank -f /backup/meine_datenbank.sql

Das erzeugt ein plaintext-SQL-Script. Für komprimierte, flexiblere Backups nutzt man das Custom-Format:


pg_dump -U postgres -d meine_datenbank -Fc -f /backup/meine_datenbank.dump

Mit -Fc erhält man ein komprimiertes Format, das later mit pg_restore selektiv wiederhergestellt werden kann (z.B. nur bestimmte Schemata oder Tabellen). pg_restore erfordert eine leere oder existierende Zieldatenbank:


createdb -U postgres meine_datenbank_restored
pg_restore -U postgres -d meine_datenbank_restored /backup/meine_datenbank.dump

WAL-Archivierung für Point-in-Time-Recovery

Ein reines pg_dump alleine genügt nicht für alle Szenarien. Wenn man Wiederherstellung auf einen bestimmten Zeitpunkt (Point-in-Time-Recovery, PITR) braucht, muss man WAL-Archivierung aktivieren:


# postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'cp %p /var/lib/postgresql/wal_archive/%f'

Diese Archivierung ist unabhängig von dem Datenbank-Backup und ermöglicht es später, ein pg_dump-Backup zu einem früheren Zeitpunkt wiederherzustellen und die fehlenden WAL-Dateien nachzuspielen, um auf den aktuellen Stand zu kommen.

Für Homelab-Umgebungen, die PITR nicht benötigen, reicht ein regelmäßiges pg_dump oft aus. Wer jedochUMAN-Charakteristiken wie Nextcloud oder Vaultwarden betreibt und im Worst-Case einen katastrophalen Datenverlust vermeiden will, sollte WAL-Archivierung in Betracht ziehen.

Alternative: pgBackRest

pgBackRest ist ein vollständiges Backup-Tool, das logische und physikalische Backups unterstützt und WAL-Archivierung verwaltet. Es behandelt Deltas, Compression und Encryption. Für komplexe Setups lohnt sich ein Blick auf die Dokumentation, doch für den typischen Homelab-Betreiber, der ein einfaches Script sucht, reicht pg_dump meist.

Der korrekte Weg für MySQL und MariaDB: mysqldump

MySQL und MariaDB bieten mysqldump (bzw. mariadb-dump, das gleiche binary unter neuem Namen) für logische Backups.

Wichtige Optionen


mysqldump --user=root --password --all-databases \
  --single-transaction \
  --routines --triggers --events \
  --flush-logs \
  -r /backup/all_databases.sql

Für einzelne Datenbanken:


mysqldump --user=root --password meine_datenbank \
  --single-transaction --routines --triggers --events \
  -r /backup/meine_datenbank.sql

MariaDB-spezifisch

mariadb-dump ist kompatibel zu mysqldump und unterstützt dieselben Optionen. Bei MariaDB mit der Aria-Engine oder speziellen MariaDB-Features lohnt ein Blick in die Dokumentation, aber für InnoDB-dominierte Setups verhält sich der Dump identisch.

Container: Backup-Jobs in docker-compose oder systemd

Im Homelab laufen Datenbanken häufig in Docker-Containern. Ein Backup-Script muss dann die richtige Umgebung finden – und die Dateirechte achten.

docker-compose-Ansatz

Im docker-compose.yml kann man einen separaten Service oder ein benutzerdefiniertes Script definieren, das das Backup durchführt:


services:
  postgres-backup:
    image: postgres:16-alpine
    entrypoint: ["sh", "-c"]
    command: >
      pg_dump -U postgres -d meine_datenbank -Fc -f /backup/meine_datenbank.dump
      && chown -R 1000:1000 /backup/
    volumes:
      - ./backups:/backup
      - postgres_data:/var/lib/postgresql/data
    user: "1000:1000"

Wichtig: Der Nutzer im Container muss die gleichen UID/GID haben wie der Besitzer des Mountpoints, oder die Datei darf nicht erstellt werden können. Alternativ lässt man den Backup-Container als root laufen und fixiert die Rechte nachträglich.

Systemd-Timer für Host-Instanzen

Für PostgreSQL oder MySQL auf dem Host selbst ist ein systemd-Timer mit einem entsprechenden Service robust:


# /etc/systemd/system/db-backup.service
[Unit]
Description=PostgreSQL logical backup
After=postgresql.service

[Service]
Type=oneshot
User=postgres
ExecStart=/usr/local/bin/pg_backup.sh

# /etc/systemd/system/db-backup.timer
[Unit]
Description=Schedule daily database backup
Requires=db-backup.service

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

Das Script pg_backup.sh kann sein:


#!/bin/bash
BACKUP_DIR="/var/backups/postgresql"
DATE=$(date +%Y%m%d_%H%M%S)
pg_dump -U postgres -d meine_datenbank -Fc -f "$BACKUP_DIR/meine_datenbank_$DATE.dump"

Dateirechte und UID/GID

Container-Volumes werden typischerweise mit den Rechten des Host-Benutzers angelegt. Der Backup-Container muss entweder die gleiche UID nutzen oder der Backup-Ordner wird mit chmod 777 (nicht empfohlen für Produktionsumgebungen) oder expliziter chown versehen. Ein sicherer Weg: Den Backup-Ordner vorher anlegen und mit der korrekten UID besitzen:


mkdir -p /var/backups/postgresql
chown 1000:1000 /var/backups/postgresql

Aufbewahrung und Verschlüsselung

Rotationsschema

Ein sinnvolles Rotationsschema für den Homelab-Betreiber:

Ein einfaches Script stösst das an:


#!/bin/bash
BACKUP_DIR="/var/backups/postgresql"
AGE_DAYS=7
AGE_WEEKS=4
AGE_MONTHS=12

find "$BACKUP_DIR" -name "*.dump" -mtime +$AGE_DAYS -delete
# wöchentliche: Prüfe mit Datum-Suffix
find "$BACKUP_DIR" -name "*_weekly_*.dump" -mtime +$(($AGE_WEEKS * 7)) -delete
# monatliche: Prüfe monatliche Backups separat
find "$BACKUP_DIR" -name "*_monthly_*.dump" -mtime +$(($AGE_MONTHS * 30)) -delete

Verschlüsselung

Backups sollten verschlüsselt werden, besonders wenn sie auf Cloud-Speicher oder externe Festplatten wandern. Ein gängiger Weg mit GPG:


gpg --symmetric --cipher-algo AES256 \
  --passphrase-file /etc/backup-credentials/gpg-passphrase \
  -o /backup/meine_datenbank.dump.gpg \
  /backup/meine_datenbank.dump

Das Passphrase-File sollte auf einem sicheren Medium (z.B. Passwort-Manager oder dezidierter Vault) gespeichert sein. Ein Backup ohne verschlüsselten Transport oder Speicher schützt nicht gegen Diebstahl des Backup-Mediums.

Wiederherstellung: Schritt-für-Schritt-Checkliste

Die Wiederherstellung ist der wichtigste Teil. Ein Backup ohne getestete Wiederherstellung ist nutzlos.

Wiederherstellungstest-Planung

Bevor man den Ernstfall erlebt, sollte man einen Wiederherstellungstest in einer isolierten Test-VM oder einem zweiten Container durchführen. Das Documentieren des Testprozesses gehört ebenfalls zum Backup-Plan.

Checkliste PostgreSQL

1. Vorbereitung: Prüfen, ob das Backup-Dump die erwartete Struktur hat (pg_restore -l backup.dump listet Inhalte auf ohne Wiederherstellung).

2. Zieldatenbank erstellen:

```bash

createdb -U postgres meine_datenbank_test

```

3. Wiederherstellung starten:

```bash

pg_restore -U postgres -d meine_datenbank_test \

--clean --if-exists \

/backup/meine_datenbank.dump

```

--clean entfernt vorhandene Objekte vor dem Wiederherstellen, --if-exists verhindert Fehler bei fehlenden Objekten.

4. Datenvalidierung: Einzelne Tabellen mit SELECT COUNT(*) oder pg_stat_user_tables überprüfen, ob Zeilenanzahl plausibel ist.

5. Anwendungstests: Nextcloud- oder Vaultwarden-Instanz mit der wiederhergestellten Datenbank starten und prüfen, ob Login, Dateien, ○○Einträge etc. funktionieren.

6. Dokumentation: Ergebnis, Dauer, eventuelle Probleme in einem Wiederherstellungstest-Protokoll festhalten.

Checkliste MySQL/MariaDB

1. Vorbereitung: Dump-Datei prüfen (head -n 50 dump.sql zeigt Struktur und Tabellen).

2. Zieldatenbank erstellen:

```bash

mysql -u root -p -e "CREATE DATABASE meine_datenbank_test;"

```

3. Wiederherstellung:

```bash

mysql -u root -p meine_datenbank_test < /backup/meine_datenbank.sql

```

Bei großen Dumps kann pv für Fortschrittsanzeige genutzt werden:

```bash

pv /backup/meine_datenbank.sql | mysql -u root -p meine_datenbank_test

```

4. Datenvalidierung: SELECT COUNT(*) FROM tabelle für kritische Tabellen, Prüfung auf Fremdschlüssel-Integrität mit mysqlcheck --check-upgrade.

5. Anwendungstests: Wie bei PostgreSQL – App lokal mit der Datenbank verbinden und prüfen.

6. Dokumentation: Wie oben.

FAQ

Wie oft muss ich ein Datenbank-Backup machen?

Das hängt vom RPO (Recovery Point Objective) ab – also wie viel Datenverlust im Worst-Case tolerierbar ist. Für kritische Dienste wie Vaultwarden (Passwörter) oder Nextcloud (Dateien, die nicht erneut erstellt werden können) ist ein tägliches Backup ratsam. Wenn die Toleranz höher ist, kann ein wöchentliches Backup genügen. Wichtig: Ein Backup ohne Wiederherstellungstest hat keinen Wert – die Häufigkeit sollte also auch die Testfrequenz berücksichtigen.

Warum funktionert cp -r des Datenbankverzeichnisses nicht?

Weil das Datenbankverzeichnis inkonsistente Zustände enthalten kann: unfertige Transaktionen, nicht geflushed buffers, fehlende WAL-Archivierung. Ein logisches Backup mit pg_dump oder mysqldump erfasst den konsistenten logischen Zustand der Datenbank, nicht den physikalischen Speicher zur Laufzeit.

Was ist der Unterschied zwischen pg_dump und WAL-Archivierung?

pg_dump erstellt ein logisches Backup des Datenbankinhalts zu einem Punkt in der Zeit. WAL-Archivierung sammelt die physikalischen Write-Ahead-Logs, die für eine kontinuierliche Point-in-Time-Recovery nötig sind. Beide zusammen ergeben ein vollständiges Disaster-Recovery-Konzept. Für viele Homelab-Umgebungen ist pg_dump alleine ausreichend, wenn der Datenverlust eines Tages tolerierbar ist.

Kann ich mein Backup verschlüsseln und trotzdem wiederherstellen?

Ja. GPG oder andere symmetrische Verschlüsselungen lassen sich vor der Wiederherstellung wieder entschlüsseln:


gpg --decrypt --passphrase-file /etc/backup-credentials/gpg-passphrase \
  /backup/meine_datenbank.dump.gpg > /backup/meine_datenbank.dump

Dasselbe gilt für symmetrische Verschlüsselung mit OpenSSL oder age. Wichtig: Der Encryption-Key muss separat und sicher gespeichert werden – sonst ist das Backup verloren.

Hinweis: Dieser Artikel ist redaktioneller Inhalt und enthält keine Affiliate- oder Partner-Links. Alle Angaben entsprechen dem Stand 2026 und können sich ändern.