Vaultwarden produktiv betreiben: Argon2-Admin-Token, Fail2Ban und sicheres Backup
Vaultwarden läuft in fünf Minuten, produktionsreif ist es damit noch nicht. Diese Anleitung zeigt den vollständigen Härtungspfad: Argon2id-Admin-Token, Fail2Ban gegen Brute-Force, HTTPS via Nginx und ein verschlüsseltes Backup, das auch den rsa_key einschließt.
Geprüft am 29.09.2026 · für vaultwarden 1.37.3
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Vaultwarden ist ein inoffizieller, in Rust geschriebener Bitwarden-Server (AGPL-3.0) für alle Bitwarden-Clients. Für Teams reicht das README-Setup nicht: Diese Anleitung ergänzt gehashten Admin-Token, Brute-Force-Schutz, Reverse-Proxy mit TLS und ein vollständiges Backup bis zum dokumentierten Restore-Test.
Voraussetzungen
- Linux-Server oder VPS (Ubuntu 24.04 LTS empfohlen), x86_64 oder ARM64, 1 bis 2 CPU-Kerne, mindestens 1 GB RAM (SQLite) bzw. 2 GB (PostgreSQL)
- Docker CE 29.x und Docker Compose v2 installiert (Docker-Installation auf Ubuntu)
- Öffentliche Domain oder Subdomain mit gültigem DNS-Eintrag (für Let's Encrypt)
- Nginx installiert, Certbot/Let's Encrypt für TLS-Zertifikat (Nginx als Reverse Proxy mit TLS einrichten)
- Fail2Ban installiert (
apt install fail2ban) - 10 GB frei; Offsite-Speicher (S3, NAS o. Ä.)
- Ca. 3 Stunden Zeit
Schritt 1: Argon2id-Admin-Token erzeugen
Seit v1.28.0 warnt Vaultwarden beim Start vor einem Klartext-ADMIN_TOKEN. Verwenden Sie einen Argon2id-PHC-Hash, am einfachsten über das Vaultwarden-Image:
# Argon2id-Hash direkt mit dem Vaultwarden-Image erzeugen:
docker run --rm -it vaultwarden/server:latest /vaultwarden hash
# Passwort eingeben und bestätigen (-it ist nötig, der Befehl fragt interaktiv)
# Ausgabe: ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$SALT$HASH'
# Alternative: lokale argon2-Binary (apt install argon2):
echo -n 'MeinSicheresPasswort' | argon2 "$(openssl rand -base64 32)" -e -id -k 65540 -t 3 -p 4Die Standardparameter m=65540 (64 MB), t=3 und p=4 entsprechen der OWASP-Empfehlung für interaktive Logins.
Verifizieren: Die Ausgabe beginnt mit ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$. Im Test mit Vaultwarden 1.37.3 dauerte die Erzeugung gut eine Sekunde.
Schritt 2: .env-Datei konfigurieren
Legen Sie die .env im Projektverzeichnis an. Wichtig: Übernehmen Sie den Hash genau wie ausgegeben: einfache $, in einfachen Anführungszeichen (sonst wertet Compose $... als Variable). $$ ist nur direkt unter environment: in der Compose-Datei nötig; in der .env behandelt Vaultwarden den Wert sonst als Klartext-Token.
# .env - Hash mit einfachen $ in einfachen Anführungszeichen
ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$SALT$HASH'
SIGNUPS_ALLOWED=false
DOMAIN=https://vault.beispiel.de
DATABASE_URL=postgresql://vwuser:sicherespasswort@db:5432/vaultwarden
LOG_FILE=/data/logs/vaultwarden.log
LOG_LEVEL=warnWebSockets laufen seit 1.29.0 über den normalen Port (ohne WEBSOCKET_ENABLED). Das Verzeichnis logs für LOG_FILE muss vor dem ersten Start existieren, sonst beendet sich der Container mit „Unable to write to log file“:
mkdir -p data/logsSetzen Sie SIGNUPS_ALLOWED=false direkt nach der Ersteinrichtung; neue Nutzer laden Sie dann über /admin → Users → Invite User ein. Empfohlen: 2FA-Pflicht auf Organisationsebene (TOTP, WebAuthn/FIDO2 oder E-Mail-OTP).
Die Adminseite erreichen Sie nach Schritt 3 und 4 unter https://vault.beispiel.de/admin. Dort geben Sie das Passwort aus Schritt 1 ein, nicht den Hash.



Verifizieren: docker compose config | grep ADMIN_TOKEN zeigt den Hash mit verdoppelten $$ (so maskiert Compose selbst). Nach dem Start liefert docker exec vaultwarden printenv ADMIN_TOKEN den Hash mit einfachen $, und im Log fehlt die Meldung „You are using a plain text ADMIN_TOKEN“.
Schritt 3: Docker Compose mit PostgreSQL
SQLite reicht für Einzelnutzer und kleine Teams, PostgreSQL bietet bei höherer Last bessere Nebenläufigkeit. Eine Migration zwischen beiden ist offiziell nicht unterstützt (siehe Häufige Fragen).
# docker-compose.yml
services:
vaultwarden:
image: ghcr.io/dani-garcia/vaultwarden:latest
container_name: vaultwarden
restart: unless-stopped
env_file: .env
volumes:
- ./data:/data
ports:
- '127.0.0.1:8080:80'
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: vwuser
POSTGRES_PASSWORD: sicherespasswort # identisch mit DATABASE_URL, eigenes Passwort wählen
POSTGRES_DB: vaultwarden
volumes:
- ./pgdata:/var/lib/postgresql/data
deploy:
resources:
limits:
cpus: '0.45'
memory: 256M127.0.0.1:8080:80 bindet Vaultwarden an localhost, erreichbar nur über Nginx. Start mit docker compose up -d.
| Datenbankbackend | Empfohlen für | Besonderheit |
|---|---|---|
| SQLite | Einzelnutzer, Heimlabor, < 5 Nutzer | Atomarer Snapshot via /vaultwarden backup (ab v1.32.1) |
| PostgreSQL | Teams, KMU, > 5 Nutzer | Backup per pg_dump -Fc, scram-sha-256-Auth empfohlen |
Verifizieren: docker compose ps zeigt beide Container als Up. curl -s http://127.0.0.1:8080/alive gibt einen Zeitstempel zurück, curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/ liefert 200. Im Test lagen beide Container unter 50 MB RAM.
Schritt 4: Nginx als Reverse Proxy mit HTTPS und WebSocket
Nötig sind WebSocket-Header und X-Real-IP: Ohne proxy_set_header X-Real-IP $remote_addr loggt es nur 127.0.0.1, und Fail2Ban-Bans greifen nicht.
# /etc/nginx/sites-available/vaultwarden.conf
upstream vaultwarden {
zone vaultwarden 64k;
server 127.0.0.1:8080;
keepalive 4;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' "";
}
# HTTP auf HTTPS weiterleiten
server {
listen 80;
listen [::]:80;
server_name vault.beispiel.de;
return 301 https://$host$request_uri;
}
# HTTPS-Server mit TLS und WebSocket-Support
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name vault.beispiel.de;
ssl_certificate /etc/letsencrypt/live/vault.beispiel.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vault.beispiel.de/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
# Vaultwarden-Standard fuer Datei-Attachments
client_max_body_size 525M;
# Security-Header
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains' always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://vaultwarden;
}
}client_max_body_size 525M entspricht dem Vaultwarden-Standard für Attachments. Danach nginx -t && systemctl reload nginx.

Verifizieren: nginx -t meldet syntax is ok. curl -I https://vault.beispiel.de liefert HTTP/2 200 mit strict-transport-security, curl -I http://vault.beispiel.de eine 301 auf HTTPS. Unter /admin → Diagnostics zeigen Proxy, IP-Header, WebSocket und Domain „Ok“ bzw. „Match“.
Schritt 5: Fail2Ban für Vaultwarden konfigurieren
Fail2Ban sperrt IPs nach wiederholten Fehlanmeldungen. Nötig sind zwei Filter: Login und Admin-Panel.
# /etc/fail2ban/filter.d/vaultwarden.conf
[Definition]
failregex = ^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =# /etc/fail2ban/filter.d/vaultwarden-admin.conf
[Definition]
failregex = ^.*Invalid admin token\. IP: <ADDR>.*$
ignoreregex =Jail in /etc/fail2ban/jail.d/vaultwarden.local. chain=FORWARD ist zwingend, weil Docker-Traffic über FORWARD statt INPUT läuft. Ab Fail2Ban v1.1.1 nutzt Debian/Ubuntu standardmäßig nftables, Docker aber iptables, daher banaction=iptables-allports explizit setzen:
# /etc/fail2ban/jail.d/vaultwarden.local
# Jail fuer regulaere Login-Versuche
[vaultwarden]
enabled = true
port = 80,443
filter = vaultwarden
logpath = /pfad/zu/vaultwarden.log
maxretry = 3
bantime = 14400
findtime = 14400
banaction = iptables-allports
chain = FORWARD
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24
# Jail fuer Admin-Panel-Login-Versuche
[vaultwarden-admin]
enabled = true
port = 80,443
filter = vaultwarden-admin
logpath = /pfad/zu/vaultwarden.log
maxretry = 3
bantime = 14400
findtime = 14400
banaction = iptables-allports
chain = FORWARD
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24Danach systemctl restart fail2ban und Filter testen:
# Filter gegen bestehende Logs testen:
fail2ban-regex /pfad/zu/vaultwarden.log /etc/fail2ban/filter.d/vaultwarden.conf
# Jail-Status pruefen:
fail2ban-client status vaultwarden
# Manueller Ban-Test:
fail2ban-client set vaultwarden banip 1.2.3.4
# Ban wieder aufheben:
fail2ban-client set vaultwarden unbanip 1.2.3.4Der Platzhalter <ADDR> ist Pflicht, sonst bricht fail2ban-regex mit „No failure-id group“ ab.
Verifizieren: fail2ban-regex /pfad/zu/vaultwarden.log /etc/fail2ban/filter.d/vaultwarden.conf meldet nach einem absichtlich falschen Login 1 matched und zeigt die Client-IP. fail2ban-client status vaultwarden listet den Jail als aktiv.
Einen umfassenderen Überblick über Fail2Ban in Kombination mit UFW gibt die Anleitung Linux-Server absichern mit UFW und Fail2Ban.
Schritt 6: Backup – alle kritischen Dateien sichern
Häufigstes Versäumnis: rsa_key.pem fehlt im Backup. Dann werden Sitzungen und Einladungslinks ungültig und alle Nutzer müssen sich neu anmelden; die Passwort-Daten bleiben erhalten.
Pflicht-Backup-Dateien: db.sqlite3 (oder PostgreSQL-Dump), attachments/, sends/, rsa_key.pem, config.json; bei älteren Installationen zusätzlich rsa_key.der und rsa_key.pub.der. Die letzten drei entstehen erst bei Bedarf.
Wichtig: config.json enthält einen über die Admin-Oberfläche gesetzten ADMIN_TOKEN im Klartext: Backups verschlüsseln, Dateirechte 600.
SQLite-Backup-Skript (ab Vaultwarden v1.32.1)
#!/bin/bash
# Vaultwarden SQLite-Backup-Skript (ab v1.32.1), als root ausführen
set -e
DATA_DIR=/opt/vaultwarden/data # Host-Pfad des Volumes ./data
BACKUP_DIR=/backup/vaultwarden
DATE=$(date +%Y%m%d-%H%M)
mkdir -p "$BACKUP_DIR/$DATE"
# Atomarer DB-Snapshot (WAL-sicher), landet als db_DATUM.sqlite3 in /data:
docker exec vaultwarden /vaultwarden backup
mv "$DATA_DIR"/db_*.sqlite3 "$BACKUP_DIR/$DATE/"
# Pflicht-Dateien kopieren (nicht vorhandene überspringen):
for f in attachments sends rsa_key.pem rsa_key.der rsa_key.pub.der config.json; do
[ -e "$DATA_DIR/$f" ] && cp -r "$DATA_DIR/$f" "$BACKUP_DIR/$DATE/"
done
# Verschlüsseltes Archiv erzeugen (GPG symmetrisch, AES-256), Klartext-Ordner löschen:
tar czf - -C "$BACKUP_DIR" "$DATE" | gpg --batch --pinentry-mode loopback \
--passphrase-file /root/.vw-backup-pass --symmetric --cipher-algo AES256 \
-o "$BACKUP_DIR/$DATE.tar.gz.gpg"
rm -rf "${BACKUP_DIR:?}/$DATE"
# Archive nach 30 Tagen rotieren:
find "$BACKUP_DIR" -maxdepth 1 -name '*.tar.gz.gpg' -mtime +30 -deleteDie Passphrase in /root/.vw-backup-pass (Rechte 600) gehört auch in den Notfallumschlag.
PostgreSQL-Backup
Der Dump läuft per docker compose exec ohne Port und Passwort:
# Täglicher Cronjob (crontab -e), im Projektverzeichnis; % muss in crontab als \% stehen:
0 2 * * * cd /opt/vaultwarden && docker compose exec -T db pg_dump -U vwuser -Fc vaultwarden > /backup/vw-db-$(date +\%Y\%m\%d).dump
# Restore:
docker compose exec -T db pg_restore -U vwuser -d vaultwarden --clean --if-exists < /backup/vw-db-20260101.dumpSichern Sie auch hier rsa_key.pem, attachments/, sends/ und config.json aus ./data. /vaultwarden backup gibt es nur für SQLite (bei PostgreSQL: „Backup failed. The database type is not SQLite“).
Automatisierung: Cron und Crontab – Grundlagen unter Linux und MySQL/PostgreSQL-Backup automatisieren mit Cron und Cloud.
Verifizieren: ls -lh /backup/vaultwarden/ zeigt eine neue .tar.gz.gpg-Datei. gpg -d DATEI.tar.gz.gpg | tar tzf - listet db_….sqlite3 und rsa_key.pem. Bei PostgreSQL zeigt docker compose exec -T db pg_restore -l < DUMP | head den Kopf des Archivs.
Schritt 7: Restore-Test durchführen
Dokumentieren Sie mindestens einen Restore-Test (Art. 32 DSGVO). Kritisch: Vor dem Restore die WAL-Dateien löschen, sonst kann eine nicht passende db.sqlite3-wal den wiederhergestellten Stand beschädigen. Pfade unter /opt/vaultwarden/data sind Host-Pfade.
# Vaultwarden stoppen:
docker compose stop vaultwarden
# Archiv entschlüsseln und entpacken:
cd /backup/vaultwarden
gpg -d 20260101-0200.tar.gz.gpg | tar xzf -
# WAL-Dateien löschen (verhindert Korruption nach Restore):
rm -f /opt/vaultwarden/data/db.sqlite3-wal /opt/vaultwarden/data/db.sqlite3-shm
# Daten zurückspielen:
cp 20260101-0200/db_*.sqlite3 /opt/vaultwarden/data/db.sqlite3
cp -r 20260101-0200/attachments /opt/vaultwarden/data/
cp 20260101-0200/rsa_key.pem /opt/vaultwarden/data/
cp 20260101-0200/config.json /opt/vaultwarden/data/
# Vaultwarden wieder starten:
docker compose start vaultwardenDanach Login mit Testkonto, Eintrag öffnen, Attachment abrufen; Datum und Ergebnis dokumentieren.
Verifizieren: docker compose ps zeigt Vaultwarden als Up, das Log enthält keine Datenbankfehler, und der Login mit dem Testkonto zeigt denselben Tresorstand wie zum Backup-Zeitpunkt.
DSGVO-Einordnung
Self-Hosting auf eigener EU-Infrastruktur vermeidet Drittland-Transfers. Die clientseitige Ende-zu-Ende-Verschlüsselung (AES-256) bleibt erhalten; auch ein kompromittierter Server liefert keine lesbaren Passwörter. Art. 32 DSGVO (technisch-organisatorische Maßnahmen) ist durch HTTPS/TLS, 2FA-Enforcement, Zugangskontrolle (SIGNUPS_ALLOWED=false) und ein dokumentiertes Backup-Konzept mit Restore-Test nachweisbar. Je nach Nutzungskontext ist eine Datenschutz-Folgenabschätzung (DSFA) empfohlen. Mehr dazu: DSGVO-TOMs und Auftragsverarbeitung in der Praxis.
Troubleshooting / Typische Fehler
- Admin-Login schlägt fehl oder Log meldet „plain text
ADMIN_TOKEN“: Hash falsch übergeben (siehe Schritt 2: in der .env einfache$in einfachen Anführungszeichen). Ein im Admin-Panel gespeicherter Token inconfig.jsonhat Vorrang vor der Umgebungsvariable. Prüfen mitdocker exec vaultwarden printenv ADMIN_TOKEN. - Fail2Ban bannt, Angreifer kommt weiter rein: nftables statt iptables aktiv.
banaction=iptables-allportsinjail.localsetzen,fail2ban-client status vaultwardenprüfen. - Fail2Ban sieht nur 127.0.0.1:
X-Real-IP-Header fehlt (Schritt 4). Bei Cloudflare zusätzlich im Vaultwarden-Admin „Client IP header: CF-Connecting-IP“ setzen. - Docker-Container-Bans greifen nicht:
chain=FORWARDfehlt im Jail. Auf Synology NAS stattdessenchain=DOCKER-USERverwenden. - SQLite-Backup ist korrupt:
cpim Betrieb sichert WAL nicht atomar. Nurdocker exec vaultwarden /vaultwarden backupodersqlite3 db.sqlite3 ".backup 'ziel.sqlite3'"verwenden. - Nach Restore alle Nutzer ausgeloggt:
rsa_key.*-Dateien fehlen im Backup oder wurden nicht zurückkopiert. - Restore scheitert an alter WAL-Datei: vorher
rm -f db.sqlite3-wal db.sqlite3-shm. - Nach Update Clients abgemeldet oder Funktionen fehlen: Release Notes lesen, Version pinnen statt
latest, etwaghcr.io/dani-garcia/vaultwarden:1.37.3.
Häufige Fragen
Muss ich den Argon2id-Hash bei jedem Vaultwarden-Update neu generieren?
Nein. Der Hash bleibt über Updates gültig; neu nur bei Änderung des Admin-Passworts.
Kann ich SQLite nachträglich durch PostgreSQL ersetzen, ohne Daten zu verlieren?
Nicht automatisch (offiziell nicht unterstützt): JSON-Export im Bitwarden-Client, frische PostgreSQL-Instanz, Re-Import. Eigene Migrationen riskieren Datenverlust.
Wie teste ich meinen Fail2Ban-Filter, bevor echte Angreifer auftauchen?
fail2ban-regex /pfad/zu/vaultwarden.log /etc/fail2ban/filter.d/vaultwarden.conf zeigt, wie viele Log-Zeilen matchen. Mit fail2ban-client set vaultwarden banip 1.2.3.4 einen manuellen Ban prüfen und mit unbanip aufheben.
Wie verhindere ich, dass Fail2Ban versehentlich den eigenen Admin sperrt?
In jail.local unter ignoreip eintragen, z. B. ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24; bei Cloudflare auch deren Exit-IPs.
Was passiert, wenn ich die rsa_key-Dateien verliere?
Sitzungen und Einladungslinks werden ungültig, die Passwort-Daten bleiben erhalten (Schritt 6).
Ist Vaultwarden DSGVO-konform einsetzbar?
Ja, siehe Abschnitt DSGVO-Einordnung.
Fazit
Produktionsreif wird Vaultwarden erst mit Argon2id-Admin-Token, SIGNUPS_ALLOWED=false, Fail2Ban und vollständigem, getestetem Backup inklusive rsa_key.pem.
Weiterführende Anleitungen und Quellen
- Linux-Server absichern mit UFW und Fail2Ban
- Nginx als Reverse Proxy mit TLS einrichten
- MySQL/PostgreSQL-Backup automatisieren mit Cron und Cloud
- DSGVO-TOMs und Auftragsverarbeitung in der Praxis
Quellen: Vaultwarden Wiki – Fail2Ban Setup (github.com/dani-garcia/vaultwarden/wiki/Fail2Ban-Setup) · Vaultwarden Wiki – Backing up your vault (github.com/dani-garcia/vaultwarden/wiki/Backing-up-your-vault) · GitHub Discussion #5407 – Argon2id Admin Token · blog.lrvt.de – Securing Vaultwarden with Fail2Ban · ComputingForGeeks – Install Vaultwarden with Nginx and Let's Encrypt (April 2026)


