Der Moment, in dem ein Backup zählt, ist selten der Moment, in dem man ruhig nachlesen kann. Server ist weg, ein frischer Hetzner steht daneben, die Dumps liegen im S3-Bucket, die Coolify-Doku ist offen. Der pg_restore läuft durch, das Dashboard kommt hoch, der Login klappt. Und beim ersten Klick auf einen konfigurierten Server steht da “The MAC is invalid.” und ein 500er.
Genau dieser Fall ist mehrfach dokumentiert, unter anderem in Issue #3761 im Coolify-Repository vom 7. Oktober 2024. Nach dem Restore auf einem neuen Server scheiterte dort zunächst der Login mit 2FA an einem 500er, und nach erfolgreichem Login lieferte jeder Aufruf einer konfigurierten S3-Storage, eines Servers oder eines Keys dieselbe Meldung. In Discussion #1684 berichtet ein Nutzer, pg_restore sei fehlerfrei durchgelaufen, das Dashboard habe sich aber schlicht nicht verändert.
Ein Satz vorweg, weil die Sache oft zu pauschal erzählt wird. Ein Datenbank-Dump aus Coolify ist ohne APP_KEY nicht wertlos. Er ist eine ganz normale Datei. Der Schlüssel entscheidet über etwas anderes.
Foto: albertstoynov / Unsplash
Was Coolify sichert und was nicht
Es gibt zwei Dinge, die beide “Backup” heißen. Das eine sind die geplanten Backups deiner Anwendungsdatenbanken. Das andere ist das Backup der Coolify-Instanz selbst, also ein pg_dump der Postgres-Datenbank coolify, in der Coolify seine gesamte Konfiguration hält.
Die Doku ist bei der Abgrenzung deutlich. Zum Instanz-Backup steht dort wörtlich: “This only backs up and restores the Coolify instance itself - not your application data.” Und ergänzend: “All settings from your Coolify dashboard will be restored, but application data (such as volume mounts) must be backed up and restored manually.”
Auf dem Host landen beide Sorten unter /data/coolify/backups. Anwendungsdatenbanken unter databases/<team-slug>-<team-id>/<verzeichnis>/, das Backup von Coolify selbst unter coolify/coolify-db-<server-ip>/. Die Dateinamen folgen dem Schema pg-dump-<db>-<unix-timestamp>.dmp, entsprechend mysql-dump-..., mariadb-dump-... und mongo-dump-<db>-<timestamp>.tar.gz. Der Zeitstempel ist ein Unix-Timestamp, kein lesbares Datum. Wer im Ernstfall den richtigen Stand sucht, rechnet also erst einmal um.
Der Upload nach S3 läuft nicht über ein AWS-SDK, sondern über einen temporären Helper-Container mit der MinIO-CLI. Coolify setzt dort ein mc alias mit den hinterlegten Zugangsdaten, kopiert die Datei per mc cp und entfernt den Container wieder. Ist disable_local_backup gesetzt, wird die lokale Kopie nach erfolgreichem Upload gelöscht.
Der Dump selbst ist unverschlüsselt
Das ist der Punkt, an dem sich die Panik sortiert. DatabaseBackupJob.php ruft für Postgres schlicht auf:
docker exec <container> pg_dump --format=custom --no-acl --no-owner \
--username <user> <db>
# mit dump_all:
docker exec <container> pg_dumpall --username <user> | gzip
MySQL und MariaDB analog mit mysqldump beziehungsweise mariadb-dump, MongoDB mit mongodump --gzip --archive. Eine Verschlüsselungsstufe auf Applikationsebene gibt es nicht. Diese Datei lässt sich überall einspielen:
pg_restore --list pg-dump-<db>-<timestamp>.dmp | head
pg_restore --clean --no-acl --no-owner -U postgres -d zielbank \
pg-dump-<db>-<timestamp>.dmp
Kein Coolify nötig, kein Schlüssel nötig. Zwei Konsequenzen daraus. Erstens hängt deine Datenrettung nicht an Coolify, solange du an den Bucket kommst. Zweitens schützt Coolify diese Dateien nicht vor jemandem, der an den Bucket kommt.
Foto: jb_baxter / Unsplash
Der APP_KEY und warum er nie im Backup steckt
Der Schlüssel steht in /data/coolify/source/.env auf dem Host:
cat /data/coolify/source/.env | grep '^APP_KEY='
# APP_KEY=base64:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
Erzeugt wird er einmalig im Installer, Zeile 842 von scripts/install.sh:
update_env_var "APP_KEY" "base64:$(openssl rand -base64 32)"
Also 32 zufällige Bytes, base64-kodiert, mit Präfix. Die Hilfsfunktion update_env_var überschreibt einen bereits gesetzten Wert nicht, sie greift nur, wenn die Zeile leer ist oder der Key komplett fehlt:
update_env_var() {
local key="$1"
local value="$2"
if grep -q "^${key}=$" "$ENV_FILE"; then
sed -i "s|^${key}=$|${key}=${value}|" "$ENV_FILE"
elif ! grep -q "^${key}=" "$ENV_FILE"; then
printf '%s=%s\n' "$key" "$value" >>"$ENV_FILE"
fi
}
Ein erneuter Lauf des Install-Skripts rotiert den Schlüssel also nicht, solange die .env existiert und den Key enthält. Praktisch relevant ist noch, dass das Skript vor jedem Merge eine datierte Kopie anlegt. Auf einem länger gelaufenen Host liegen deshalb potenziell Dateien wie .env-20260727-113000, und die können einen älteren Schlüssel enthalten:
ls -la /data/coolify/source/.env-*
grep -h '^APP_KEY=' /data/coolify/source/.env-* | sort -u
Im Backup ist der Schlüssel nicht. Das Instanz-Backup ist ein pg_dump der Datenbank, die .env gehört nicht dazu. Die Doku warnt an der Stelle wörtlich: “Save this APP_KEY safely. Without it, you cannot restore your backup.” Issue #350 in den Coolify-Docs, eröffnet am 31. August 2025 und weiterhin offen, empfiehlt als Prävention ausdrücklich, .env-Dateien getrennt von den Datenbank-Backups zu sichern.
Was Coolify verschlüsselt
Coolify legt einzelne Spalten seiner eigenen Datenbank mit Laravels Encrypter ab, erkennbar am Cast 'encrypted' in den Models:
// PrivateKey.php
protected $casts = ['private_key' => 'encrypted'];
// EnvironmentVariable.php
protected $casts = ['key' => 'string', 'value' => 'encrypted', /* ... */];
// StandalonePostgresql.php
protected $casts = [/* ... */ 'postgres_password' => 'encrypted', /* ... */];
// S3Storage.php
protected $casts = ['is_usable' => 'boolean', 'key' => 'encrypted', 'secret' => 'encrypted'];
Dazu kommen die Logdrain-Schlüssel im Server-Model. Zusammengefasst sind das die SSH-Keys für alle Zielserver, sämtliche Environment-Variablen aller Anwendungen, die Datenbank-Passwörter und die S3-Zugangsdaten.
Wer wie ich alle Environment-Variablen von Produktions-Apps konsequent über Coolify setzt und nicht in lokalen Dateien pflegt, macht den APP_KEY damit zum Single Point of Failure. Das ist die saubere Betriebsvariante, denn die Secrets liegen dann nirgends im Repo. Sie hat nur genau diesen Preis.
Foto: 2hmedia / Unsplash
Was ohne den Schlüssel passiert
Laravel verschlüsselt mit AES-256-CBC und signiert jeden Wert mit einem MAC. Passt der Schlüssel nicht, wirft der Encrypter eine DecryptException mit einer von drei Meldungen:
throw new DecryptException('The MAC is invalid.'); // Encrypter.php, Zeile 188
throw new DecryptException('Could not decrypt the data.'); // Zeile 192
throw new DecryptException('The payload is invalid.'); // Zeilen 235 und 244
Das Symptombild aus Issue #350 ist charakteristisch. Der Login ins Dashboard klappt, neue Ressourcen lassen sich anlegen, bestehende nicht mehr öffnen. Die Doku führt dazu den Troubleshooting-Fall “500 Error on Login or Project Access”. In Issue #11056 vom 27. Juli 2026 (Coolify v4.1.2, Ubuntu 24.04.4) liefert eine Service-Seite HTTP 500 mit “The payload is invalid.”, der Stacktrace führt über Encrypter.php:244 nach EnvironmentVariable.php:354. Der Meldende hat per Crypt::decryptString() in artisan tinker nachgewiesen, dass praktisch jede Zeile der Spalte environment_variables.value unter dem aktiven Schlüssel nicht mehr entschlüsselbar war, und fand keine Kopie des alten Schlüssels. Ob dort tatsächlich ein rotierter APP_KEY die Ursache war, ist offen, eine Maintainer-Antwort gibt es bisher nicht, und die Logik von update_env_var spricht eher dagegen.
Besonders unangenehm ist eine Rückkopplung. Coolify kann aus der Oberfläche direkt aus S3 zurückspielen, über Ressource, Configuration, “Import Backup”, “Select from S3”. Dieser Weg holt sich die Zugangsdaten über S3Storage::ownedByCurrentTeam()->findOrFail($this->s3StorageId), und key und secret dieses Models sind verschlüsselt. Ohne passenden Schlüssel kann Coolify seine eigenen S3-Credentials nicht lesen. Der bequeme Restore-Weg fällt also genau dann aus, wenn man ihn braucht. Wer die S3-Zugangsdaten unabhängig notiert hat, umgeht das und lädt die Dumps direkt.
Der dokumentierte Weg zurück
Die Doku beschreibt acht Schritte. Backup auslösen, APP_KEY sichern, SSH-Keys aus /data/coolify/ssh/keys sichern (Dateien ssh_key@<random_id>) plus die Public Keys aus ~/.ssh/authorized_keys, neuen Server mit derselben Coolify-Version installieren, Dump einspielen, Keys zurücklegen, APP_PREVIOUS_KEYS eintragen, Install-Skript erneut laufen lassen.
docker stop coolify coolify-redis coolify-realtime coolify-proxy
cat /path/to/your_backup_file | docker exec -i coolify-db \
pg_restore --verbose --clean --no-acl --no-owner -U coolify -d coolify
Warnungen zu bestehenden Foreign Keys oder Sequenzen kann man laut Doku üblicherweise ignorieren. Danach die Keys:
rm -f /data/coolify/ssh/keys/*
# alte ssh_key@<random_id>-Dateien hineinkopieren, danach:
sudo chown -R root:root /data/coolify
Der chown ist kein Kosmetikschritt, sondern das dokumentierte Gegenmittel gegen “Permission denied” beim Serverzugriff.
Und dann die Stelle mit der Formatfalle:
APP_KEY="base64:J63qRTDLub5NuZvP+kb8YIorGS6qFYHKVo6u7179stY="
APP_PREVIOUS_KEYS="base64:2nLsGFGzyoae2ax3EF2Lyq/hH6QghBGLIq5uL+Gp8/w="
APP_PREVIOUS_KEYS ist ein Laravel-Feature. Verschlüsselt wird immer mit dem aktuellen Schlüssel, beim Entschlüsseln probiert Laravel zuerst den aktuellen und danach alle gelisteten alten durch, kommagetrennt. Die Coolify-Doku schreibt den Wert an dieser Stelle ohne Präfix (APP_PREVIOUS_KEYS=your_previous_app_key_here), Laravel und Issue #350 zeigen ihn mit base64:. Übernimm den Wert exakt so, wie er in der alten .env stand.
Was das Backup nicht enthält
Volumes. Die Migrations-Doku formuliert das unmissverständlich: “Coolify does not have a built-in option to migrate applications from one server to another.” Der dokumentierte Weg ist ein Wegwerf-Container:
docker run --rm -v "$VOLUME_NAME":/volume -v "$(pwd)/$BACKUP_DIR":/backup \
busybox tar czf /backup/"$BACKUP_FILE" -C /volume .
docker run --rm -v "$TARGET_VOLUME":/volume -v "$(pwd)/$BACKUP_DIR":/backup \
busybox sh -c "cd /volume && tar xzf /backup/$BACKUP_FILE"
Das trifft jedes per API angehängte Persistent-Storage-Volume. Ein Uploads-Volume, das auf /app/uploads gemountet ist und hochgeladene Kundendateien hält, fällt unter keinen Datenbank-Zeitplan. Wer nur Datenbank-Backups konfiguriert hat, hat die Dateien nicht.
Beim Import über die Oberfläche sind außerdem Redis, KeyDB, Dragonfly und ClickHouse als unsupported markiert. Issue #7529 dokumentiert die Matrix, nach der StandalonePostgresql, Mysql, Mariadb und Mongodb Backup und Restore konnten, ServiceDatabase aus Docker-Compose-Deployments dagegen nur Backup. Das Issue ist am 1. April 2026 geschlossen worden, einen Merge-Beleg für eine Implementierung habe ich nicht gefunden. Ich würde das für die eigene Umgebung nachprüfen, statt es anzunehmen.
Foto: ubahnverleih / Unsplash
Eine Restore-Probe, die man tatsächlich durchziehen kann
Ein offizielles Verifikationsverfahren gibt es nicht. Aus den dokumentierten Schritten lässt sich aber eine Probe zusammensetzen, die die interessanten Stellen wirklich anfasst.
Zuerst das Material einsammeln und außerhalb des Servers ablegen, im Passwortmanager oder in einem verschlüsselten Tresor. Dazu gehören der APP_KEY, alle .env-*-Kopien mit abweichendem Key, die Dateien aus /data/coolify/ssh/keys, die S3-Zugangsdaten im Klartext und die laufende Coolify-Version.
Dann ein Zweitserver mit derselben Version, Instanz-Dump einspielen, Keys zurücklegen, alten Schlüssel als APP_PREVIOUS_KEYS eintragen. Der eigentliche Test beginnt danach, und er besteht nicht aus einem erfolgreichen Login. Öffne gezielt eine S3-Storage, einen Server, einen Private Key und die Environment-Variablen einer Anwendung. Genau diese vier Ansichten kippen bei falschem Schlüssel in einen 500er, alles andere sieht heil aus.
Zweiter Teil, unabhängig davon: einen Anwendungs-Dump aus S3 gegen eine leere Postgres außerhalb von Coolify einspielen und einen Volume-Tarball in ein frisches Volume zurückrollen. Danach den Zweitserver wegwerfen.
Ich prüfe bei Coolify grundsätzlich per curl gegen, ob eine Änderung wirklich am Container angekommen ist, statt der Erfolgsmeldung im Dashboard zu glauben. Bei Container-Labels lohnt sich das, weil Docker sie nur beim Create liest. Beim Restore lohnt es sich mehr. Ein grüner pg_restore sagt über den entscheidenden Teil nichts aus. Wenn du deine Serverlandschaft ohnehin gerade sortierst, ist der Vergleich der Hosting-Optionen in Hetzner gegen AWS und Azure ein passender Nachbar zu diesem Thema.
Wenn du Coolify selbst betreibst und nicht sicher bist, ob dein Backup im Ernstfall trägt, schau ich mir das gern an. Ein Blick auf die .env, die Backup-Zeitpläne und die Frage, welche Volumes gerade in keinem Zeitplan stehen, reicht meistens für eine klare Antwort. Erreichbar bin ich über das Kontaktformular oder direkt per Mail.
Häufige Fragen
Ist ein Coolify-Backup in S3 ohne den APP_KEY wertlos?+
Nein, das gilt nur für das Backup der Coolify-Instanz selbst. Die Dumps deiner Anwendungsdatenbanken erzeugt Coolify mit pg_dump, mysqldump, mariadb-dump oder mongodump und legt sie ohne zusätzliche Verschlüsselung ab. Wer die S3-Zugangsdaten unabhängig von Coolify notiert hat, kommt jederzeit an diese Dateien und kann sie von Hand in eine beliebige Datenbank einspielen. Verloren ist ohne Schlüssel der Inhalt von Coolifys eigener Datenbank, also SSH-Keys, Env-Variablen, DB-Passwörter und S3-Credentials.
Wo finde ich den APP_KEY meiner Coolify-Instanz?+
In der Datei /data/coolify/source/.env auf dem Coolify-Host, als Zeile APP_KEY=base64:... Die Doku nennt genau diesen Schritt im Restore-Ablauf. Zusätzlich legt das Install-Skript vor jedem Merge eine datierte Kopie der Datei an, sodass auf einem gelaufenen Host auch Dateien wie .env-20260727-113000 liegen können, die ältere Schlüssel enthalten.
Wofür ist APP_PREVIOUS_KEYS da?+
Das ist ein Laravel-Feature, kein Coolify-Feature. Laravel verschlüsselt immer mit dem aktuellen APP_KEY, probiert beim Entschlüsseln aber zuerst den aktuellen und danach alle in APP_PREVIOUS_KEYS gelisteten Schlüssel durch, kommagetrennt. Beim Restore auf einem neuen Server trägt man dort den alten Schlüssel ein. Wichtig ist die exakte Übernahme des Werts inklusive base64-Präfix, denn die Coolify-Doku zeigt an dieser Stelle ein Beispiel ohne Präfix.
Sichert Coolify auch Docker-Volumes?+
Nein. Die Migrations-Doku sagt dazu ausdrücklich, dass Coolify keine eingebaute Möglichkeit hat, Anwendungen von einem Server auf einen anderen zu migrieren, und verlangt, Datenbanken und Volumes manuell zu kopieren. Der dokumentierte Weg für Volumes ist ein Wegwerf-Container, der das Volume und ein Backup-Verzeichnis mountet und den Inhalt per tar sichert.
Du willst mehr erfahren?
In einem kostenlosen Erstgespräch besprechen wir, wie du diese Themen für dein Unternehmen nutzen kannst. Kein Verkaufsgespräch, sondern eine ehrliche Einschätzung.
Kostenloses Erstgespräch vereinbaren



