Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Sicherheit & Datenschutz 04.06.2026 · 10 min Lesezeit

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 wasserdichtes Backup, das auch den rsa_key einschließt.

Gehärtetes digitales Tresorschloss mit Schutzschichten als Symbol für Vaultwarden-Sicherheit

Vaultwarden ist ein inoffizieller, in Rust geschriebener Bitwarden-Server (AGPL-3.0), der auf minimaler Hardware läuft und alle Bitwarden-Clients unterstützt. Das schnelle Docker-Setup aus dem README reicht für den Heimgebrauch – wer Vaultwarden jedoch für ein Team oder ein Unternehmen einsetzt, braucht deutlich mehr: einen gehashten Admin-Token, der keine Klartext-Warnung mehr auslöst, Schutz vor Brute-Force-Angriffen, einen Reverse-Proxy mit gültigem TLS-Zertifikat und ein Backup-Konzept, das wirklich alle kritischen Dateien sichert. Diese Anleitung deckt den vollständigen Härtungspfad ab – vom Argon2id-Hash bis zum dokumentierten Restore-Test.

Voraussetzungen

  1. Linux-Server oder VPS (Ubuntu 24.04 LTS empfohlen), mindestens 1 GB RAM für SQLite-Betrieb, 2 GB für PostgreSQL
  2. Docker CE 29.x und Docker Compose v2 installiert (Docker-Installation auf Ubuntu)
  3. Öffentlich erreichbare Domain oder Subdomain mit validem DNS-Eintrag (Let's Encrypt benötigt diesen)
  4. Nginx installiert, Certbot/Let's Encrypt für TLS-Zertifikat (Nginx als Reverse Proxy mit TLS einrichten)
  5. Fail2Ban installiert (apt install fail2ban)
  6. Mindestens 10 GB freier Speicher für Daten und Backups; sicherer Offsite-Speicher (S3, NAS oder vergleichbar)
  7. Ca. 3 Stunden Zeit für Setup, Tests und erste Backup-Verifikation

Schritt 1: Argon2id-Admin-Token erzeugen

Seit Vaultwarden v1.28.0 erscheint beim Start eine explizite Warnung, wenn ADMIN_TOKEN als Klartext gesetzt ist. Verwende ausschließlich einen Argon2id-PHC-Hash. Die einfachste Methode nutzt das Vaultwarden-Image selbst:

# Argon2id-Hash direkt mit dem Vaultwarden-Image erzeugen: docker run --rm -it vaultwarden/server:latest /vaultwarden hash # Passwort eingeben und bestätigen # Ausgabe: $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 4

Die Standardparameter m=65540 (64 MB), t=3 (Iterationen) und p=4 (Parallelität) entsprechen der OWASP-Empfehlung für interaktive Logins. Den Hash nicht verändern – er ist stabil über alle Vaultwarden-Updates hinweg und muss nur bei Passwortänderung neu generiert werden.

Schritt 2: .env-Datei konfigurieren

Lege die Datei .env im Projektverzeichnis an. Wichtig: In .env-Dateien muss jedes $ im Argon2id-Hash als $$ maskiert werden – Docker Compose und die meisten .env-Parser interpretieren $... sonst als Shellvariable, der Token wird leer übergeben und der Admin-Login schlägt mit „wrong token" fehl.

# .env – alle $ im Argon2id-Hash als $$ maskieren! 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=warn WEBSOCKET_ENABLED=true

Setze SIGNUPS_ALLOWED=false direkt nach der Ersteinrichtung. Neue Nutzer werden danach ausschließlich über das Admin-Panel unter /admin → Users → Invite User eingeladen. Zusätzlich empfiehlt sich 2FA-Enforcement auf Organisationsebene (TOTP, WebAuthn/FIDO2 oder E-Mail-OTP).

Schritt 3: Docker Compose mit PostgreSQL

SQLite reicht für Einzelnutzer und kleine Teams. Für Mehrbenutzerbetrieb mit höherer Last bietet PostgreSQL bessere Nebenläufigkeit. Eine direkte Migration von SQLite zu PostgreSQL ist offiziell nicht unterstützt – plane einen sauberen Neustart mit JSON-Export und Re-Import über den Bitwarden-Client ein.

# 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 POSTGRES_DB: vaultwarden volumes: - ./pgdata:/var/lib/postgresql/data deploy: resources: limits: cpus: '0.45' memory: 256M

Der Port 127.0.0.1:8080:80 bindet Vaultwarden ausschließlich an localhost – von außen ist der Dienst nur über den Nginx-Reverse-Proxy erreichbar. Starte den Stack mit docker compose up -d.

DatenbankbackendEmpfohlen fürBesonderheit
SQLiteEinzelnutzer, Heimlabor, < 5 NutzerAtomarer Snapshot via /vaultwarden backup (ab v1.32.1)
PostgreSQLTeams, KMU, > 5 NutzerBackup per pg_dump -Fc, scram-sha-256-Auth empfohlen

Schritt 4: Nginx als Reverse Proxy mit HTTPS und WebSocket

Vaultwarden benötigt korrekte WebSocket-Header für Echtzeit-Benachrichtigungen sowie X-Real-IP, damit Fail2Ban die echten Client-IPs sieht. Ohne proxy_set_header X-Real-IP $remote_addr loggt Vaultwarden nur 127.0.0.1 – Bans greifen dann nicht.

upstream vaultwarden { zone vaultwarden 64k; server 127.0.0.1:8080; keepalive 4; } map $http_upgrade $connection_upgrade { default upgrade; '' ""; } server { listen 80; listen [::]:80; server_name vault.beispiel.de; return 301 https://$host$request_uri; } 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; client_max_body_size 525M; 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; } }

Der Wert client_max_body_size 525M entspricht dem Vaultwarden-Standard für Datei-Attachments. Nach dem Speichern nginx -t && systemctl reload nginx ausführen.

Schritt 5: Fail2Ban für Vaultwarden konfigurieren

Fail2Ban liest das Vaultwarden-Log und sperrt IP-Adressen nach wiederholten fehlgeschlagenen Anmeldeversuchen. Zwei Filter sind nötig: einer für das reguläre Login, einer für das Admin-Panel.

# /etc/fail2ban/filter.d/vaultwarden.conf [Definition] failregex = ^.*Username or password is incorrect\.\sTry again\.\sIP:\s<ADDR>\.\sUsername:.*$ ignoreregex =
# /etc/fail2ban/filter.d/vaultwarden-admin.conf [Definition] failregex = ^.*Invalid admin token\.\sIP:\s<ADDR>.*$ ignoreregex =

Der Jail wird in /etc/fail2ban/jail.d/vaultwarden.local definiert. Zwei kritische Punkte für Docker-Setups: chain=FORWARD ist zwingend, weil Docker-Traffic nicht über INPUT, sondern über FORWARD läuft. Auf Debian/Ubuntu ab Fail2Ban v1.1.1 ist der Standard-banaction auf nftables umgestellt – Docker verwendet jedoch iptables, daher muss banaction=iptables-allports explizit gesetzt werden:

# /etc/fail2ban/jail.d/vaultwarden.local [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 [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/24

Nach dem Speichern: systemctl restart fail2ban. Den Filter direkt gegen bestehende Logs testen, bevor der erste echte Angriff eintrifft:

# Filter testen: fail2ban-regex /pfad/zu/vaultwarden.log /etc/fail2ban/filter.d/vaultwarden.conf # Jail-Status prüfen: fail2ban-client status vaultwarden # Manueller Ban/Unban-Test: fail2ban-client set vaultwarden banip 1.2.3.4 fail2ban-client set vaultwarden unbanip 1.2.3.4

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

Das häufigste Backup-Versäumnis bei Vaultwarden: Die Datenbank wird gesichert, rsa_key.pem aber nicht. Fehlt dieser Schlüssel, werden alle aktiven Sitzungen sofort invalidiert und alle ausstehenden Einladungslinks ungültig. Die eigentlichen Passwort-Daten bleiben erhalten – aber alle Nutzer müssen sich neu anmelden und neue Einladungen versenden.

Pflicht-Backup-Dateien: db.sqlite3 (oder PostgreSQL-Dump), attachments/, sends/, rsa_key.pem, rsa_key.pub.pem, config.json.

Wichtig: config.json enthält den ADMIN_TOKEN im Klartext, wenn er über die Admin-Weboberfläche gesetzt wurde. Backup-Verschlüsselung ist daher Pflicht, Dateiberechtigungen auf 600 setzen.

SQLite-Backup-Skript (ab Vaultwarden v1.32.1)

#!/bin/bash BACKUP_DIR=/backup/vaultwarden DATE=$(date +%Y%m%d-%H%M) mkdir -p $BACKUP_DIR/$DATE # Atomarer DB-Snapshot (WAL-sicher, ab v1.32.1): docker exec vaultwarden /vaultwarden backup # Alle Pflicht-Dateien kopieren: cp -r /data/attachments $BACKUP_DIR/$DATE/ cp -r /data/sends $BACKUP_DIR/$DATE/ cp /data/rsa_key.pem $BACKUP_DIR/$DATE/ cp /data/rsa_key.pub.pem $BACKUP_DIR/$DATE/ cp /data/config.json $BACKUP_DIR/$DATE/ # Verschlüsseltes Archiv erzeugen (GPG symmetrisch, AES-256): tar czf - $BACKUP_DIR/$DATE | gpg --symmetric --cipher-algo AES256 -o $BACKUP_DIR/$DATE.tar.gz.gpg # Lokale Kopien nach 30 Tagen rotieren: find $BACKUP_DIR -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;

PostgreSQL-Backup

# Passwort-Datei einrichten (einmalig): echo 'localhost:5432:vaultwarden:vwuser:sicherespasswort' >> ~/.pgpass && chmod 600 ~/.pgpass # Täglicher Cronjob (crontab -e): 0 2 * * * pg_dump -U vwuser -h localhost -Fc vaultwarden > /backup/vw-db-$(date +%Y%m%d).dump # Restore: pg_restore -U vwuser -h localhost -d vaultwarden /backup/vw-db-20260101.dump

Zur Backup-Automatisierung per Cron siehe auch Cron und Crontab – Grundlagen unter Linux sowie die detaillierte Anleitung MySQL/PostgreSQL-Backup automatisieren mit Cron und Cloud.

Schritt 7: Restore-Test durchführen

Ein Backup ohne Restore-Test ist kein Backup. Führe den Test mindestens einmal durch und dokumentiere ihn – das ist auch für Art. 32 DSGVO relevant. Kritisch: Vor dem Restore immer die WAL-Dateien löschen, sonst wird der wiederhergestellte Stand sofort durch eine existierende db.sqlite3-wal überschrieben.

# Vaultwarden stoppen: docker compose stop vaultwarden # WAL-Dateien löschen (verhindert Korruption nach Restore): rm -f /data/db.sqlite3-wal /data/db.sqlite3-shm # Daten zurückspielen: cp /backup/vaultwarden/20260101-0200/db.sqlite3 /data/db.sqlite3 cp -r /backup/vaultwarden/20260101-0200/attachments /data/ cp /backup/vaultwarden/20260101-0200/rsa_key.pem /data/ cp /backup/vaultwarden/20260101-0200/rsa_key.pub.pem /data/ cp /backup/vaultwarden/20260101-0200/config.json /data/ # Vaultwarden wieder starten: docker compose start vaultwarden

Nach dem Start: Login mit einem Testkonto prüfen, einen Passwort-Eintrag öffnen und ein Attachment abrufen. Dokumentiere Backup-Datum, Restore-Zeitpunkt und Testergebnis.

DSGVO-Einordnung

Self-Hosting von Vaultwarden auf eigener EU-Infrastruktur eliminiert Drittland-Transfers vollständig. Die Ende-zu-Ende-Verschlüsselung (AES-256 clientseitig) bleibt dabei serverseitig 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 zusätzlich eine Datenschutz-Folgenabschätzung (DSFA) empfohlen. Weiterführende Informationen zu TOMs und Auftragsverarbeitung bietet die Anleitung DSGVO-TOMs und Auftragsverarbeitung in der Praxis.

Troubleshooting / Typische Fehler

  1. Admin-Login schlägt mit „wrong token" fehl: Das $-Zeichen im Argon2id-Hash in der .env-Datei ist nicht als $$ maskiert. Jeden einzelnen $ durch $$ ersetzen (gilt nur für .env-Dateien; beim direkten docker run -e-Parameter kein Doppel-$$).
  2. Fail2Ban bannt zwar, aber Angreifer kommt weiter rein: Vermutlich greift nftables statt iptables. Explizit banaction=iptables-allports in jail.local setzen und fail2ban-client status vaultwarden prüfen.
  3. Fail2Ban sieht nur 127.0.0.1 als IP: Nginx fehlt proxy_set_header X-Real-IP $remote_addr. Cloudflare-Nutzer müssen zusätzlich im Vaultwarden-Admin „Client IP header: CF-Connecting-IP" setzen.
  4. Docker-Container-Bans greifen nicht: chain=FORWARD fehlt im Jail. Auf Synology NAS stattdessen chain=DOCKER-USER verwenden.
  5. SQLite-Backup ist korrupt: Einfaches cp während des Betriebs sichert den WAL-Zustand nicht atomar. Ausschließlich docker exec vaultwarden /vaultwarden backup oder sqlite3 db.sqlite3 ".backup 'ziel.sqlite3'" verwenden.
  6. Nach Restore sind alle Nutzer ausgeloggt: rsa_key.pem fehlt im Backup oder wurde nicht mitgespielt. Alle vier Schlüsseldateien müssen im Backup enthalten und beim Restore kopiert werden.
  7. Restore schlägt wegen alter WAL-Datei fehl: Vor dem Restore rm -f db.sqlite3-wal db.sqlite3-shm ausführen – sonst wird der Restore-Stand sofort durch die alte WAL überschrieben.
  8. Vaultwarden-Version 1.35.1 – Argon2-Parsing-Fehler: Diese Version hatte ein temporäres Parsing-Problem mit Argon2-Token. Immer die aktuelle stabile Version verwenden und das Changelog vor Produktions-Updates lesen.

Häufige Fragen

Muss ich den Argon2id-Hash bei jedem Vaultwarden-Update neu generieren?

Nein. Der Hash ändert sich nicht durch Updates. Ein neuer Hash ist nur nötig, wenn das Admin-Passwort geändert werden soll. Den bestehenden Hash in der .env-Datei einfach unverändert belassen.

Kann ich SQLite nachträglich durch PostgreSQL ersetzen, ohne Daten zu verlieren?

Eine automatische Migration ist offiziell nicht unterstützt. Der empfohlene Weg: Passwörter im Bitwarden-Client als JSON exportieren, eine frische PostgreSQL-Instanz einrichten, Vaultwarden neu starten und die Daten re-importieren. Eine direkte Datenbankmigration auf eigene Faust birgt das Risiko von Datenverlust.

Wie teste ich meinen Fail2Ban-Filter, bevor echte Angreifer auftauchen?

Mit fail2ban-regex /pfad/zu/vaultwarden.log /etc/fail2ban/filter.d/vaultwarden.conf kann die Regex direkt gegen bestehende Log-Zeilen getestet werden. Die Ausgabe zeigt an, wie viele Zeilen gematcht wurden. Anschließend mit fail2ban-client set vaultwarden banip 1.2.3.4 einen manuellen Ban prüfen und mit unbanip wieder aufheben.

Wie verhindere ich, dass Fail2Ban versehentlich den eigenen Admin sperrt?

In jail.local die eigene IP-Adresse oder das eigene Subnetz unter ignoreip eintragen: ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24. Cloudflare-Exit-IPs sollten ebenfalls eingetragen werden, wenn der Traffic über Cloudflare läuft.

Was passiert, wenn ich die rsa_key-Dateien verliere?

Alle aktiven Sitzungen werden sofort invalidiert – alle eingeloggten Nutzer werden ausgeloggt. Ausstehende Einladungslinks (Organisations-Einladungen per E-Mail) werden ungültig. Die eigentlichen Passwort-Daten in der Datenbank bleiben vollständig erhalten; nach erneutem Login ist alles wieder zugänglich. Die Schlüsseldateien gelten als vertraulich und sollten im Backup verschlüsselt gespeichert werden.

Ist Vaultwarden DSGVO-konform einsetzbar?

Self-Hosting auf eigener EU-Infrastruktur ist generell DSGVO-günstiger als Cloud-Dienste, da keine Drittland-Transfers stattfinden. Die clientseitige Ende-zu-Ende-Verschlüsselung (AES-256) bleibt unabhängig vom Server erhalten – auch ein kompromittierter Server liefert keine lesbaren Passwörter. Art.-32-Anforderungen (technisch-organisatorische Maßnahmen) sind durch HTTPS, 2FA, Zugangskontrolle und ein dokumentiertes Backup nachweisbar. Eine DSFA ist je nach Nutzungskontext empfohlen.

Fazit

Vaultwarden ist eine solide Grundlage für Self-Hosted-Passwortverwaltung – aber die fünf Minuten des ersten Setups sind keine Produktionsreife. Der Argon2id-Admin-Token eliminiert das Klartext-Passwort-Problem, SIGNUPS_ALLOWED=false schließt die offene Registrierung, Fail2Ban schützt vor automatisierten Angriffen, und ein vollständiges Backup mit rsa_key.pem und dokumentiertem Restore-Test macht das System im Ernstfall wiederherstellbar. Wer diese Schritte konsequent umsetzt, betreibt einen Passwortserver, der auch in einer KMU-Umgebung bestehen kann.

Weiterführende Anleitungen und Quellen

  1. Linux-Server absichern mit UFW und Fail2Ban – Grundlagen zu Fail2Ban-Konfiguration und UFW-Firewall
  2. Nginx als Reverse Proxy mit TLS einrichten – SSL-Zertifikate, Certbot und Nginx-Konfiguration
  3. MySQL/PostgreSQL-Backup automatisieren mit Cron und Cloud – pg_dump-Strategien und Cloud-Offsite-Backup
  4. DSGVO-TOMs und Auftragsverarbeitung in der Praxis – Art.-32-konforme Dokumentation technisch-organisatorischer Maßnahmen

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)