Zum Inhalt

RFC-005: Backup-Konzept fuer OVH-Server (Borg/borgmatic)

Feld Wert
Status draft
Datum 2026-04-02
Betrifft novabrands-mgmt, homelab-external, homelab-documentation
Tracking-Issues noch keine
Verwandt

Problem

Der OVH RISE-S Server (Frankfurt, Ryzen 9700X, 64 GB RAM) betreibt produktive Dienste fuer Novabrands (OpenProject, Nextcloud, Coder, Speakr) ohne jegliche Backup-Strategie. Ein Datenverlust durch Hardware-Defekt, Software-Fehler oder menschliches Versagen wuerde zu vollstaendigem Verlust der Projektdaten, Dateien und Konfigurationen fuehren.

Konkrete Risiken:

  1. Kein Backup: Aktuell existiert kein automatisiertes Backup
  2. 3 PostgreSQL-Datenbanken: OpenProject, Nextcloud und Coder speichern geschaeftskritische Daten in Datenbanken ohne Sicherung
  3. Nextcloud-Dateien: Benutzerdateien liegen ausschliesslich auf dem Server
  4. Keine Georedundanz: Alle Daten befinden sich an einem Standort (OVH Frankfurt)
  5. Kein definiertes RPO/RTO: Keine Vorgabe wie viel Datenverlust akzeptabel ist

Ist-Zustand

Server

Parameter Wert
Anbieter OVH RISE-S (Dedicated)
Standort Frankfurt
Hardware AMD Ryzen 7 9700X, 64 GB DDR5, 2x512 GB NVMe RAID-1
IP 51.77.84.41
OS Ubuntu
Repo-Pfad /opt/containers/novabrands-mgmt/

Dienste und Datenbanken

Dienst Datenbank Volumes
Traefik Certs (/opt/containers/traefik/certs), Config
OpenProject PostgreSQL 18 (openproject-db) op-assets
Nextcloud PostgreSQL 18 (nextcloud-db) nc-html, nc-data
Collabora — (stateless)
Coder PostgreSQL 18 (coder-db)
Speakr SQLite (in Volume) speakr-uploads, speakr-instance
WhisperX ASR speakr-asr-cache (regenerierbar)

Bestehende Vorarbeit

Ein dump-databases.sh Skript existiert bereits und erstellt pg_dump-Exports aller drei PostgreSQL-Datenbanken nach /opt/containers/novabrands-mgmt/db-dumps/. Fuer Nextcloud wird korrekt der Maintenance-Mode aktiviert/deaktiviert.

Vorschlag

Implementierung einer 3-2-1-Backup-Strategie mit BorgBackup und borgmatic:

  • 3 Kopien: Produktionsdaten + OVH Backup Storage + Hetzner Storage Box
  • 2 Medien: Lokaler NFS-Mount (OVH) + Remote SSH (Hetzner)
  • 1 Offsite: Hetzner Storage Box (Falkenstein)

Das Konzept orientiert sich am bewaehrten Backup-Setup des Homeservers (borgmatic 1.8.14, Hetzner Storage Box, 3-2-1-Strategie).

Architektur

OVH RISE-S Server (Frankfurt)
├── borgmatic (Host-nativ, systemd-Timer)
│   ├── Pre-Hook: dump-databases.sh (pg_dump x3)
│   ├── Borg create → OVH Backup Storage (NFS)
│   ├── Borg create → Hetzner Storage Box (SSH)
│   ├── Borg prune + compact (beide Repos)
│   └── Post-Hook: Healthchecks-Ping
├── /mnt/backup-ovh/borg/          ← NFS-Mount, lokales Borg-Repo
│   └── OVH Backup Storage (500 GB, Frankfurt)
└── ssh://uXXXXXX@uXXXXXX.your-storagebox.de:23/./novabrands/
    └── Hetzner Storage Box BX11 (1 TB, Falkenstein)

Zwei unabhaengige Borg-Repos statt eines gespiegelten Repos:

Repo Ziel Protokoll Zweck
ovh-local OVH Backup Storage (500 GB) NFS-Mount → lokales Repo Schnelles Backup + Restore
hetzner-offsite Hetzner Storage Box BX11 (1 TB) Borg nativ via SSH (Port 23) Offsite, Georedundanz

Vorteile gegenueber einem gespiegelten Repo:

  • Repo-Korruption in einem Repo betrifft nicht das andere
  • Unabhaengige Prune-/Retention-Policies moeglich
  • Kein Konsistenzproblem durch rsync auf laufendes Repo

Backup-Scope

Datenbank-Dumps (Pre-Hook):

Datenbank Dump-Methode Besonderheit
openproject-db docker execpg_dump --format=custom
nextcloud-db docker execpg_dump --format=custom Maintenance-Mode ein/aus
coder-db docker execpg_dump --format=custom

Dumps werden nach /opt/containers/novabrands-mgmt/db-dumps/ geschrieben (bestehendes Skript dump-databases.sh als Basis).

Docker-Volumes:

Volume Dienst Inhalt
op-assets OpenProject Anhaenge, Avatare
nc-html Nextcloud Anwendungsdateien
nc-data Nextcloud Benutzerdateien
speakr-uploads Speakr Hochgeladene Audiodateien
speakr-instance Speakr SQLite-DB, Konfiguration

Host-Pfade:

Pfad Inhalt
/opt/containers/novabrands-mgmt/ docker-compose.yml, .env, Traefik-Config
/opt/containers/novabrands-mgmt/db-dumps/ Datenbank-Dumps
/opt/containers/traefik/certs Let's Encrypt Zertifikate
/etc/ System-Konfiguration
/root/.ssh/ SSH-Keys (root)
/home/ubuntu/.ssh/ SSH-Keys (ubuntu)
/var/spool/cron/ Cronjobs

Explizit ausgeschlossen:

Ausschluss Begruendung
op-pgdata, nc-pgdata, coder-pgdata Konsistente pg_dumps statt roher DB-Dateien
speakr-asr-cache Regenerierbares ML-Modell-Cache (~4-8 GB)
/var/log/ Logs sind nicht backup-relevant
/tmp/, /var/tmp/ Temporaere Dateien

Zeitplan und RPO

Recovery Point Objective (RPO): 12 Stunden

Uhrzeit Backup Begruendung
02:00 Nacht-Backup Geringste Last, konsistenteste Daten
14:00 Nachmittag-Backup Sichert Aenderungen des Arbeitstages

Reihenfolge pro Lauf:

  1. Pre-Hook: dump-databases.sh (DB-Dumps erstellen)
  2. borg create → OVH-Repo (NFS, schnell)
  3. borg create → Hetzner-Repo (SSH, langsamer)
  4. borg prune + borg compact (beide Repos)
  5. Post-Hook: Healthchecks-Ping (Success/Failure)

Retention-Policy

keep_daily: 14
keep_weekly: 8
keep_monthly: 12

Geschaetzter Speicherbedarf bei unter 50 GB Quelldaten und Borg-Deduplizierung (~60-80% Ersparnis): 30-80 GB pro Repo. Beide Targets (500 GB OVH, 1 TB Hetzner) bieten ausreichend Reserve.

Verschluesselung

Algorithmus: repokey-blake2 (AES-256-CTR + BLAKE2b-256)

  • Schluessel im Repo gespeichert, passwortgeschuetzt
  • Passphrase in /opt/containers/novabrands-mgmt/.env (gitignored)
  • Schluessel-Export (borg key export) muss bei Einrichtung erstellt und sicher verwahrt werden (z.B. Proton Pass)

Schluessel-Backup

Ohne den Borg-Key und die Passphrase sind die Backups unwiederbringlich verloren. Der Key-Export ist ein kritischer Schritt bei der Einrichtung.

Kompression

compression: zstd,3

zstd bietet das beste Verhaeltnis aus Kompressionsrate und CPU-Verbrauch. Level 3 ist der Standard-Sweet-Spot (45-50% Kompression bei geringer CPU-Last).

Monitoring und Alerting

Integration in die bestehende Monitoring-Infrastruktur auf dem Hetzner-Server:

borgmatic (OVH)
  → Healthchecks (Hetzner, self-hosted)
    → ntfy (Hetzner, Push-Notifications)

Zwei separate Healthchecks-Checks:

Check Erwartung Grace Period
novabrands-backup-nacht Ping alle 24h um ~02:00 2 Stunden
novabrands-backup-nachmittag Ping alle 24h um ~14:00 2 Stunden

Bei ausbleibendem Ping (Dead-Man-Switch) sendet Healthchecks eine Benachrichtigung ueber ntfy. borgmatic hat native Healthchecks-Integration und pingt automatisch bei Start, Erfolg und Fehler.

Deployment: Host-nativ

borgmatic wird direkt auf dem Host installiert, nicht als Docker-Container.

Begruendung:

Kriterium Host-nativ Docker-Container
DB-Dump-Hooks docker exec direkt verfuegbar Docker-Socket-Mount oder DinD noetig
NFS-Mount Trivial via /etc/fstab Mount auf Host, Durchreichung als Volume
Volume-Zugriff Direkter Zugriff auf /var/lib/docker/volumes/ Alle Volumes einzeln mounten
Debugging Einfach (borg list, borg info) Erfordert docker exec
Abhaengigkeiten borgbackup, borgmatic Pakete Image-Management
Updates apt upgrade oder pip install --upgrade Image-Tag aktualisieren

Das bestehende Homeserver-Setup nutzt ebenfalls borgmatic auf dem Host und hat sich bewaehrt. Die Vorteile der Container-Isolierung wiegen die Komplexitaet der Docker-Socket- und Volume-Durchreichung nicht auf.

Installation:

# borgbackup via apt (stabiler, OS-integriert)
apt install borgbackup

# borgmatic via pipx (aktuelle Version, isoliert)
pipx install borgmatic

Systemd-Timer statt Cron:

# /etc/systemd/system/borgmatic.timer
[Unit]
Description=borgmatic backup timer

[Timer]
OnCalendar=*-*-* 02:00:00
OnCalendar=*-*-* 14:00:00
RandomizedDelaySec=15min
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true stellt sicher, dass ein verpasstes Backup (z.B. bei Reboot) nachgeholt wird.

borgmatic-Konfiguration (Entwurf)

# /etc/borgmatic.d/novabrands.yaml
source_directories:
  - /opt/containers/novabrands-mgmt
  - /opt/containers/traefik/certs
  - /var/lib/docker/volumes/novabrands-mgmt_op-assets
  - /var/lib/docker/volumes/novabrands-mgmt_nc-html
  - /var/lib/docker/volumes/novabrands-mgmt_nc-data
  - /var/lib/docker/volumes/novabrands-mgmt_speakr-uploads
  - /var/lib/docker/volumes/novabrands-mgmt_speakr-instance
  - /etc
  - /root/.ssh
  - /home/ubuntu/.ssh
  - /var/spool/cron

repositories:
  - path: /mnt/backup-ovh/borg
    label: ovh-local
  - path: ssh://uXXXXXX@uXXXXXX.your-storagebox.de:23/./novabrands
    label: hetzner-offsite

exclude_patterns:
  - '*.pyc'
  - '__pycache__'
  - '*.tmp'
  - '*.log'
  - '/etc/shadow'
  - '/etc/gshadow'

encryption_passcommand: cat /opt/containers/novabrands-mgmt/.borg-passphrase

compression: zstd,3

retention:
  keep_daily: 14
  keep_weekly: 8
  keep_monthly: 12

hooks:
  before_backup:
    - /opt/containers/novabrands-mgmt/dump-databases.sh
  healthchecks:
    ping_url: https://healthchecks.robinwerner.net/ping/{uuid}

Volume-Pfade

Die Docker-Volume-Pfade (/var/lib/docker/volumes/...) muessen nach Deployment verifiziert werden. Das Volume-Praefix haengt vom Compose-Projekt-Namen ab (Standard: Verzeichnisname novabrands-mgmt).

NFS-Mount (OVH Backup Storage)

# /etc/fstab
ftpback-rbxX-YYY.ovh.net:/export/ftpbackup/nsXXXX.ip-X-X-X.eu \
  /mnt/backup-ovh nfs rw,soft,intr,nfsvers=3 0 0
  • Aktivierung im OVH Control Panel (Dedicated → Server → Backup Storage)
  • IP-basierte Autorisierung fuer NFS konfigurieren
  • Mount-Verzeichnis: /mnt/backup-ovh/
  • Borg-Repo in Unterverzeichnis: /mnt/backup-ovh/borg/

Hetzner Storage Box

Parameter Wert
Produkt BX11
Kapazitaet 1 TB
Kosten ~3,81 EUR/Monat
Protokoll Borg nativ via SSH (Port 23)
Standort Falkenstein

Einrichtung:

  1. Storage Box im Hetzner Robot bestellen
  2. SSH-Key auf der Storage Box hinterlegen
  3. Sub-Account fuer Novabrands-Backups erstellen (Zugriffstrennung)
  4. borg init auf dem Remote-Repo ausfuehren
# SSH-Key generieren
ssh-keygen -t ed25519 -f /root/.ssh/hetzner-borg-novabrands -N ""

# Key auf Storage Box hinterlegen
cat /root/.ssh/hetzner-borg-novabrands.pub | \
  ssh -p 23 uXXXXXX@uXXXXXX.your-storagebox.de install-ssh-key

# Repo initialisieren
borg init --encryption=repokey-blake2 \
  ssh://uXXXXXX@uXXXXXX.your-storagebox.de:23/./novabrands

Restore-Verfahren

Einzelne Dateien:

# Letztes Archiv listen
borg list /mnt/backup-ovh/borg

# Datei extrahieren
borg extract /mnt/backup-ovh/borg::ARCHIV-NAME \
  opt/containers/novabrands-mgmt/docker-compose.yml

Datenbank-Restore:

# Dump aus Backup extrahieren
borg extract /mnt/backup-ovh/borg::ARCHIV-NAME \
  opt/containers/novabrands-mgmt/db-dumps/nextcloud.dump

# In Container kopieren und wiederherstellen
docker cp nextcloud.dump nextcloud-db:/tmp/
docker exec nextcloud-db pg_restore \
  -U nextcloud -d nextcloud --clean /tmp/nextcloud.dump

Vollstaendiges Disaster Recovery:

  1. Server neu aufsetzen (Ubuntu, Docker)
  2. borgbackup + borgmatic installieren
  3. Borg-Key und Passphrase aus sicherem Speicher holen
  4. NFS-Mount einrichten ODER von Hetzner Storage Box wiederherstellen
  5. Konfiguration extrahieren (/opt/containers/novabrands-mgmt/)
  6. DB-Dumps extrahieren und wiederherstellen
  7. Docker-Volumes extrahieren
  8. docker compose up -d

Geschaetzte Recovery Time (RTO):

Szenario Quelle Geschaetzte Dauer
Einzelne Datei OVH (NFS) < 5 Minuten
Einzelne Datei Hetzner (SSH) < 15 Minuten
Datenbank-Restore OVH (NFS) 10-30 Minuten
Vollstaendiges DR OVH (NFS) 1-2 Stunden
Vollstaendiges DR Hetzner (SSH) 2-4 Stunden (netzwerkabhaengig)

Betroffene Repos

Repo Aenderung
novabrands-mgmt borgmatic-Config, Passphrase in .env, dump-databases.sh anpassen, NFS-fstab, systemd-Timer, SSH-Key fuer Hetzner
homelab-external Healthchecks: 2 neue Checks fuer Novabrands-Backups anlegen, ntfy-Alerting konfigurieren
homelab-documentation RFC-005, Backup-Dokumentation, LikeC4-Modell um Backup-Flows ergaenzen

Umsetzungsschritte

Phase 1: Infrastruktur vorbereiten

  1. OVH Backup Storage im Control Panel aktivieren (falls nicht geschehen)
  2. NFS-Zugang fuer Server-IP autorisieren
  3. Hetzner Storage Box BX11 bestellen
  4. SSH-Key fuer Hetzner Storage Box generieren und hinterlegen
  5. Sub-Account auf der Storage Box erstellen

Phase 2: borgmatic einrichten

  1. borgbackup und borgmatic auf dem Server installieren
  2. NFS-Mount in /etc/fstab einrichten und testen
  3. Borg-Repo auf OVH Backup Storage initialisieren (borg init)
  4. Borg-Repo auf Hetzner Storage Box initialisieren (borg init)
  5. Borg-Keys exportieren und sicher verwahren (Proton Pass)
  6. borgmatic-Konfiguration erstellen (/etc/borgmatic.d/novabrands.yaml)
  7. dump-databases.sh als borgmatic-Pre-Hook integrieren
  8. Passphrase in .borg-passphrase ablegen (restriktive Rechte: chmod 600)

Phase 3: Testen

  1. Manuelles Backup ausfuehren (borgmatic --verbosity 1)
  2. Backup-Integritaet pruefen (borgmatic check)
  3. Restore-Test: Einzelne Datei extrahieren
  4. Restore-Test: Datenbank-Dump wiederherstellen (auf Test-Container)
  5. Backup-Groesse und -Dauer dokumentieren

Phase 4: Automatisierung und Monitoring

  1. Systemd-Timer einrichten (02:00 + 14:00)
  2. Healthchecks-Checks auf dem Hetzner-Server anlegen
  3. ntfy-Alerting fuer fehlgeschlagene Backups konfigurieren
  4. Healthchecks-UUID in borgmatic-Config eintragen
  5. Ersten automatischen Lauf abwarten und verifizieren

Phase 5: Dokumentation

  1. Backup-Dokumentation in homelab-documentation erstellen
  2. LikeC4-Modell um Backup-Beziehungen erweitern
  3. Restore-Runbook schreiben (Schritt-fuer-Schritt fuer DR)

Offene Aktionspunkte

  • OVH Backup Storage NFS-Adresse und Mount-Optionen verifizieren
  • Hetzner Storage Box bestellen und Sub-Account einrichten
  • Docker-Volume-Pfade auf dem Server verifizieren (Praefix abhaengig vom Compose-Projekt)
  • Borg-Key-Export sicher verwahren (Proton Pass)
  • Healthchecks-Checks anlegen und UUIDs dokumentieren
  • Erster Restore-Test nach Einrichtung
  • Backup-Groesse nach erstem Lauf dokumentieren
  • Entscheidung: Separate Passphrase pro Repo oder gemeinsame Passphrase?
  • dump-databases.sh: Fehlerbehandlung verbessern (Abbruch bei fehlgeschlagenem Dump)

Risiken

Risiko Mitigation
Borg-Key oder Passphrase verloren Key-Export bei Einrichtung erstellen, in Proton Pass speichern. Beide Repos haben eigene Keys — Verlust betrifft nur ein Repo.
NFS-Mount nicht verfuegbar soft,intr Mount-Optionen verhindern Haenger. borgmatic meldet Fehler an Healthchecks. Hetzner-Backup laeuft unabhaengig weiter.
Hetzner Storage Box nicht erreichbar OVH-Backup laeuft unabhaengig. Alerting via Healthchecks bei Fehler.
Backup-Fenster zu lang (Netzwerk-Engpass) Bei unter 50 GB Quelldaten und Borg-Deduplizierung sind inkrementelle Backups klein (~MB bis wenige GB). Initial-Backup kann laenger dauern.
Nextcloud-Dateien inkonsistent ohne Maintenance Mode Maintenance Mode wird im Pre-Hook aktiviert. Kurze Downtime (~Sekunden fuer DB-Dump) ist akzeptabel.
DB-Dump schlaegt fehl, Backup laeuft trotzdem dump-databases.sh mit set -euo pipefail — Fehler bricht Pre-Hook ab, borgmatic stoppt.
OVH Backup Storage voll (500 GB) Monitoring der Repo-Groesse. Bei 50 GB Quelldaten und Retention 14d/8w/12m geschaetzte Nutzung 30-80 GB — ausreichend Reserve.
Kein regelmaessiger Restore-Test Quartalsweisen Restore-Test als Reminder in Healthchecks einplanen.

Kosten

Posten Kosten
OVH Backup Storage (500 GB) 0 EUR/Monat (im Server inklusive)
Hetzner Storage Box BX11 (1 TB) ~3,81 EUR/Monat
Healthchecks + ntfy (self-hosted) 0 EUR (bestehende Infrastruktur)
Gesamt ~3,81 EUR/Monat

Zukunftsaussichten

  • Borg 2.0 Migration: Sobald Borg 2.0 stabil ist, Migration evaluieren (neue Repository-Architektur, bessere Performance)
  • Nextcloud-Daten wachsen: Bei deutlichem Wachstum ggf. Storage Box upgraden (BX21, 5 TB) oder separate Nextcloud-Backup-Strategie
  • Weitere Server anbinden: Das Konzept ist auf zusaetzliche Server uebertragbar (gleiche borgmatic-Konfiguration, eigene Repos)
  • Append-Only-Mode: Fuer zusaetzlichen Schutz gegen Ransomware koennte das Hetzner-Repo im Append-Only-Mode betrieben werden (Pruning dann nur ueber separaten Zugang)