Mailserver sind selten „stateless“: Postfächer, Indexe, Datenbanken, DKIM-Keys, Konfigurationen – alles lebt auf dem Host. Fällt die Maschine aus oder wird kompromittiert, entscheidet ein funktionierendes Backup über Minuten oder Tage Ausfall. Genau hier setzt mein Repo mailcow-cloudflare-r2-backup an: Es automatisiert Mailcow-Backups und synchronisiert sie zuverlässig in ein S3-kompatibles Object Storage – konkret Cloudflare R2 – inklusive Retention (Aufbewahrungszeit), Restore-Funktion und Cron-tauglicher Bedienung.
In diesem Artikel zeige ich dir praxisnah: welches Problem das Setup löst, wie die Architektur aussieht, wie du es Schritt für Schritt installierst, wie du Cron/Automation sauber taktest, welche Sicherheitsmaßnahmen sinnvoll sind, wie du Monitoring aufsetzt und welche Troubleshooting-Fälle in der Realität auftreten.
Problem: Lokale Mailcow-Backups sind nicht genug
Mailcow bringt ein solides Backup-Skript mit. Viele Installationen lassen es nachts laufen und legen die Archive lokal unter /backups/ ab. Das ist ein guter Start – aber es bleibt ein Single-Point-of-Failure: Wenn die Platte stirbt, der Host verschlüsselt wird oder ein Operator versehentlich aufräumt, sind lokale Backups wertlos.
Die typischen Anforderungen in Projekten sind deshalb:
- Offsite-Backup (physisch/logisch getrennt vom Mailserver)
- Automatische Aufbewahrung (z. B. 7–30 Tage) ohne manuelles Löschen
- Wiederherstellung ohne Basteln (idealerweise interaktiv auswählbar)
- Planbar per Cron und robust gegen Parallel-Läufe
- Nachvollziehbarkeit über Logs und einfache Checks
Cloudflare R2 ist dafür attraktiv, weil es S3-kompatibel ist, sich gut automatisieren lässt und in vielen Setups als kosteneffizienter Object Storage genutzt wird. Das Repo kombiniert Mailcow-Backup-Erzeugung (Mailcow selbst) mit Upload/Retention/Restore (Script + rclone).
Architektur: Mailcow erzeugt Backups, Script synchronisiert nach R2
Das Setup besteht aus drei klar getrennten Bausteinen:
- Mailcow Backup Job: erzeugt regelmäßig Backup-Verzeichnisse unter /backups/ (z. B. mailcow-YYYY-MM-DD-...)
- cloudflare-r2 Script: synchronisiert die Backup-Verzeichnisse nach Cloudflare R2, verwaltet Retention und bietet Restore
- rclone: übernimmt die S3-kompatible Kommunikation mit Cloudflare R2 (Endpoint, Credentials, Transfer)
Wichtig: Das Script ist Mailcow-optimiert, weil es die typische Backup-Struktur und Namensmuster berücksichtigt. Dadurch kann es gezielt „Backup-Ordner“ erkennen und sauber verwalten, statt blind alles aus einem Verzeichnis zu kopieren.
Setup Schritt für Schritt (de-DE, produktionsnah)
1) Voraussetzungen prüfen
- Mailcow läuft stabil (typisch unter /opt/mailcow-dockerized)
- Ein lokales Backup-Ziel existiert und hat genug Platz (z. B. /backups/)
- Cloudflare R2 ist eingerichtet: Bucket vorhanden, Access Key/Secret erstellt
- Du hast Root-/sudo-Zugriff auf den Mailcow-Host
Realistische Caveats: Plane lokalen Speicher so, dass mindestens 1–2 vollständige Backups parallel liegen können (Mailcow erzeugt neue Backups, bevor du alte löschst). Außerdem solltest du Upload-Zeiten berücksichtigen: Je nach Größe (5–20 GB sind typisch) kann der Transfer mehrere Minuten bis Stunden dauern.
2) rclone installieren
Das Repo setzt auf rclone. Installation gemäß README:
curl https://rclone.org/install.sh | sudo bashDanach prüfen:
rclone version3) rclone für Cloudflare R2 konfigurieren (S3-kompatibel)
Starte die Konfiguration:
rclone configWähle dann (wie im README beschrieben):
- Storage: s3
- Provider: Cloudflare
- Access Key ID / Secret Access Key: aus dem R2 Dashboard
- Endpoint: https://<your-account-id>.r2.cloudflarestorage.com
- Remote Name: z. B. cloudflare-backup
Caveat aus der Praxis: Der Endpoint muss exakt stimmen (Account-ID), sonst bekommst du später „No such host“ oder Auth-Fehler. Außerdem solltest du den Remote-Namen konsistent halten, weil er in der Script-Konfiguration referenziert wird.
4) Repo installieren
Installation gemäß README:
cd ~
git clone https://github.com/alandolsi/mailcow-cloudflare-r2-backup.git
cd mailcow-cloudflare-r2-backup
chmod +x install.sh
sudo ./install.shDas Install-Skript legt die ausführbaren Komponenten unter /opt/cloudflare_r2_backup/ ab (siehe README-Pfad in der Konfiguration) und stellt die Bedienung über den Befehl cloudflare-r2 bereit.
5) backup.env anpassen (die zentrale Konfiguration)
Öffne die Konfiguration:
sudo nano /opt/cloudflare_r2_backup/backup.envEin funktionierendes Beispiel (aus dem README, an deine Domain anpassen):
export SOURCE="/backups/"
export RETENTION_DAYS=7export
RCLONE_DEST="cloudflare-backup:backups/mail.example.com"
export BACKUP_PATTERN='^mailcow-[0-9]{4}-[0-9]{2}-[0-9]{2}/$'
export LOGFILE="/var/log/backup_sync.log"Was diese Variablen in der Praxis bedeuten:
- SOURCE: Wo Mailcow die Backups ablegt. Für Mailcow ist /backups/ der Standard in vielen Setups.
- RETENTION_DAYS: Wie lange Backups in R2 behalten werden. 7 Tage ist ein guter Start; für Compliance/Forensik oft 14–30.
- RCLONE_DEST: Ziel in R2 im Format remote:bucket/prefix. Hier ist cloudflare-backup dein rclone-Remote, und backups/mail.example.com ein sauberer Prefix pro Host/Domain.
- BACKUP_PATTERN: Regex, um nur gültige Mailcow-Backup-Ordner zu erkennen. Das reduziert das Risiko, versehentlich falsche Ordner zu löschen.
- LOGFILE: Zentrales Log für Monitoring und Troubleshooting.
Caveat: Achte darauf, dass das Ziel pro Mailserver eindeutig ist (z. B. FQDN). Sonst überschreiben/vermischen sich Backups mehrerer Systeme im gleichen Prefix.
Cron/Automation: richtig takten, damit nichts kollidiert
Der wichtigste Punkt bei Automatisierung ist die Reihenfolge: Erst muss Mailcow das Backup erzeugen, danach darf der Upload laufen. Im README ist ein bewährtes Timing-Beispiel: Mailcow um 01:00 Uhr, Upload um 04:00 Uhr.
Öffne Root-Crontab:
sudo crontab -eFüge die Jobs hinzu (aus dem README):
# Mailcow creates backup at 1:00 AM (keeps 1 day locally)0 1 * * * cd /opt/mailcow-dockerized/; MAILCOW_BACKUP_LOCATION=/backups/ /opt/mailcow-dockerized/helper-scripts/backup_and_restore.sh backup all --delete-days 1# Upload to Cloudflare R2 at 4:00 AM (keeps 7 days in cloud)0 4 * * * cloudflare-r2 backupWarum diese Aufteilung sinnvoll ist:
- Lokale Retention kurz halten (z. B. 1 Tag), um Plattenplatz zu sparen.
- Cloud-Retention länger (z. B. 7 Tage), weil R2 als Offsite-Backup dient.
- Genug Abstand zwischen Backup-Erzeugung und Upload, damit große Backups nicht „halbfertig“ synchronisiert werden.
Caveat: Wenn deine Backups sehr groß sind oder der Server nachts stark ausgelastet ist, kann 3 Stunden Abstand knapp werden. Dann Upload später starten oder Mailcow-Backup früher.
Cloudflare R2: Struktur, Kosten und praktische Empfehlungen
In R2 solltest du eine klare Bucket-/Prefix-Strategie fahren:
- Ein Bucket für Backups (z. B. backups)
- Pro Mailserver ein Prefix (z. B. backups/mail.example.com)
Das erleichtert Restore, Berechtigungen und spätere Migrationen. Das README empfiehlt als Kosten-/Sicherheits-Balance häufig RETENTION_DAYS=7. In der Praxis gilt: Je höher die Änderungsrate (viele Postfächer, große Anhänge), desto eher lohnen 14–30 Tage, weil du sonst „leise“ Datenkorruption oder verspätet bemerkte Löschungen nicht abfangen kannst.
Sicherheit: Credentials, Rechte, Restore-Disziplin
Credentials minimieren
Die R2 Access Keys sind der Schlüssel zu deinen Backups. Best Practices:
- Keys nur auf dem Mailserver speichern, nicht auf Admin-Laptops
- Konfiguration und Logs mit restriktiven Rechten betreiben (Root-only, wo sinnvoll)
- Remote/Prefix so wählen, dass ein Key nicht unnötig breit Zugriff hat (wenn du mehrere Systeme trennst, trenne auch Prefix/Bucket)
Restore ist ein Eingriff – plane ihn wie ein Change
Ein Restore überschreibt Daten. Halte dich an die Mailcow-Schritte aus dem README und stoppe Mailcow vor dem Restore:
- Mailcow stoppen
- Backup aus R2 herunterladen (interaktiv oder gezielt)
- Mailcow Restore-Skript ausführen
- Mailcow wieder starten
Wenn du nur testen willst, nutze den Restore in ein Custom Location-Verzeichnis (README unterstützt das), um Inhalte zu prüfen, ohne produktive Daten zu überschreiben.
Monitoring: Logs, letzte erfolgreiche Läufe, R2-Inventar
Das Repo setzt auf ein Logfile (Standard: /var/log/backup_sync.log). Damit bekommst du schnell Klarheit, ob der Job läuft.
Live-Log:
tail -f /var/log/backup_sync.logLetzten erfolgreichen Abschluss finden (aus dem README):
grep "Backup End" /var/log/backup_sync.log | tail -1Backups in R2 auflisten (aus dem README):
rclone lsf cloudflare-backup:backups/mail.example.com --dirs-onlyPraxis-Tipp: Ergänze serverseitig ein einfaches Health-Check-Signal, z. B. indem du täglich prüfst, ob „Backup End“ innerhalb der letzten 24 Stunden vorkommt. Das ist oft aussagekräftiger als nur „Cron lief“, weil Uploads auch fehlschlagen können.
Troubleshooting: die häufigsten Fehlerbilder
„No backup folders found on R2“
Das README nennt die zwei häufigsten Ursachen:
- RCLONE_DEST zeigt auf den falschen Bucket/Prefix
- rclone Remote ist nicht korrekt konfiguriert
Prüfe zuerst, ob der Remote erreichbar ist:
rclone lsd cloudflare-backup:Wenn das klappt, prüfe den konkreten Pfad in RCLONE_DEST und ob deine Backups dem BACKUP_PATTERN entsprechen.
„All files already synced“
Laut README ist das normal: Es bedeutet, dass aktuell nichts Neues hochzuladen ist. Erwartbar, wenn Mailcow seit dem letzten Lauf kein neues Backup erzeugt hat oder wenn du den Upload manuell mehrfach startest.
Update schlägt fehl
Das Repo bietet eine Self-Update-Funktion. Wenn sie scheitert, nennt das README als pragmatischen Workaround ein manuelles Update:
- Ins Repo-Verzeichnis wechseln
- git pull ausführen
Wichtig ist, dass die Installation so erfolgt ist, wie im README vorgesehen (Symlink-/Install-Skript-Ansatz). Wenn du Dateien manuell kopiert hast, kann die Update-Logik ins Leere laufen.
Restore-Workflow (kompakt, aber sicher)
Für den Ernstfall ist der Ablauf im README klar und bewährt:
- Mailcow stoppen: docker-compose down
- Restore aus R2 starten: cloudflare-r2 restore (interaktiv) oder gezielt per Namen
- Mailcow-Restore ausführen: backup_and_restore.sh restore auf das heruntergeladene Verzeichnis
- Mailcow starten: docker-compose up -d
Caveat: Plane Downtime ein. Ein Restore ist nicht „online“. Je nach Datenmenge kann der Download aus R2 und das Einspielen dauern. Teste den Ablauf mindestens einmal in einer Wartungsphase, bevor du ihn im Incident brauchst.
Fazit
Mit mailcow-cloudflare-r2-backup bekommst du eine praxisnahe Offsite-Backup-Kette für Mailcow: Mailcow erzeugt Backups lokal, das Script synchronisiert sie nach Cloudflare R2, verwaltet Retention und bietet eine Restore-Funktion, die auch unter Stress bedienbar bleibt. Wenn du Cron-Timing, saubere Prefix-Struktur, restriktive Credentials und ein Minimum an Monitoring kombinierst, hast du ein Setup, das im Alltag „einfach läuft“ – und im Ernstfall wirklich rettet.