Aller au contenu
Sécurité medium

Vaultwarden : héberger son gestionnaire de mots de passe

11 min de lecture

Logo Vaultwarden

Vaultwarden est un serveur compatible Bitwarden, réécrit en Rust, conçu pour le self-hosting léger. Il parle la même API que Bitwarden, donc les clients officiels (extension, mobile, bureau) fonctionnent avec lui sans modification. Ce guide le déploie avec Docker Compose, explique pourquoi le HTTPS est obligatoire, et couvre les pièges réels : ADMIN_TOKEN, fermeture des inscriptions, sauvegardes et durcissement. Lab vérifié sur la version 1.36.0.

  • Déployer Vaultwarden avec Docker Compose
  • Comprendre pourquoi HTTPS est obligatoire et le mettre en place
  • Sécuriser le panneau admin (ADMIN_TOKEN) et fermer les inscriptions
  • Sauvegarder le coffre sans le corrompre

Vaultwarden n'est pas un produit Bitwarden officiel : c'est une réimplémentation communautaire de l'API, non affiliée à Bitwarden, Inc. Son atout est la légèreté : une seule image, une base SQLite, une empreinte mémoire minime, là où le serveur Bitwarden officiel est bien plus lourd à héberger.

Le revers est la maturité de processus. Vaultwarden n'a pas de certification SOC 2, pas d'équipe sécurité dédiée ni d'audit formel, contrairement à Bitwarden officiel. Il convient très bien à un homelab, une famille ou une petite équipe qui maîtrise HTTPS et les sauvegardes. Pour un usage régulé ou entreprise (SSO, conformité, support éditeur), c'est le serveur Bitwarden officiel qu'il faut.

Créez un docker-compose.yml. On épingle la version (1.36.0) plutôt que latest, pour un déploiement reproductible.

services:
vaultwarden:
image: vaultwarden/server:1.36.0
container_name: vaultwarden
restart: unless-stopped
environment:
DOMAIN: "https://vault.exemple.fr"
SIGNUPS_ALLOWED: "true" # temporaire : on fermera après création du compte
volumes:
- ./vw-data:/data
ports:
- "127.0.0.1:11001:80" # exposé en local seulement, derrière le reverse proxy

Le conteneur écoute en interne sur le port 80. On le publie sur 127.0.0.1:11001 : il n'est pas exposé directement à Internet, le reverse proxy s'en chargera (étape 2). Démarrez et suivez les logs :

Fenêtre de terminal
docker compose up -d && docker compose logs -f

Au démarrage, Vaultwarden annonce sa version et lance son serveur :

| Starting Vaultwarden |
| Version 1.36.0 |
[start][INFO] Rocket has launched from http://0.0.0.0:80

Vérifiez qu'il répond, sans même ouvrir un navigateur :

Fenêtre de terminal
curl -s http://127.0.0.1:11001/alive # -> un horodatage ISO
curl -s http://127.0.0.1:11001/api/version # -> "1.36.0"

Une réponse sur /alive et la version sur /api/version confirment que le serveur tourne et que le volume vw-data est bien initialisé.

C'est le piège n°1, et il n'est pas négociable. Le coffre web de Bitwarden utilise les Web Crypto APIs du navigateur, que les navigateurs ne rendent disponibles que dans un contexte sécurisé : https://, ou http://127.0.0.1 sur la machine locale. Depuis une autre machine en HTTP simple, l'interface se charge mais toutes les opérations de chiffrement échouent silencieusement : le déverrouillage tourne en boucle, la connexion reste bloquée sur un écran de chargement.

Ce n'est pas une invention de Vaultwarden, c'est une règle des navigateurs imposée par les standards : aucune option ne la contourne. La nuance utile : c'est HTTPS qui est obligatoire, pas le reverse proxy en lui-même. Mais en pratique, le reverse proxy est la voie la plus simple pour obtenir un certificat valide, gérer le renouvellement et contrôler les en-têtes.

Caddy est le choix recommandé pour débuter : il obtient et renouvelle le certificat Let's Encrypt automatiquement. Un Caddyfile minimal :

vault.exemple.fr {
reverse_proxy 127.0.0.1:11001
}

Caddy gère seul le TLS et transmet correctement les en-têtes WebSocket (Upgrade, Connection) nécessaires aux notifications temps réel (activées par défaut depuis la 1.29.0, sur le port HTTP standard depuis la 1.31.0). La variable DOMAIN du compose doit correspondre à l'URL HTTPS publique.

Vaultwarden expose un panneau d'administration sur /admin (configuration serveur, gestion des utilisateurs, diagnostics). Il est protégé par un ADMIN_TOKEN. Ne mettez jamais un mot de passe en clair : depuis la 1.28.0, Vaultwarden avertit si le token n'est pas un hash argon2id. Générez-le avec la commande intégrée, en mode interactif (un vrai terminal est requis) :

Fenêtre de terminal
docker run --rm -it vaultwarden/server:1.36.0 /vaultwarden hash --preset owasp

La commande demande le mot de passe deux fois puis affiche une chaîne PHC argon2id de cette forme :

$argon2id$v=19$m=19456,t=2,p=1$<sel-base64>$<hash-base64>

Mieux : si vous n'utilisez le panneau admin qu'une fois pour la configuration initiale, désactivez-le ensuite. Retirer la variable ne suffit pas si la valeur a été persistée : assurez-vous qu'aucun ADMIN_TOKEN n'est défini ET qu'aucune clé admin_token n'existe dans config.json, puis redémarrez. S'il reste actif, restreignez /admin par VPN ou liste d'IP au niveau du reverse proxy : c'est une surface d'attaque à forte valeur, à ne jamais exposer publiquement.

Avec SIGNUPS_ALLOWED: "true", n'importe qui atteignant l'URL peut créer un compte sur votre serveur. Créez votre compte, puis passez la variable à false et redémarrez.

Trois pièges à connaître :

  • Même avec les inscriptions fermées, le formulaire reste visible ; le refus n'apparaît qu'à la soumission.
  • Si SIGNUPS_DOMAINS_WHITELIST est défini, SIGNUPS_ALLOWED est ignoré : une whitelist mal réglée peut rouvrir les inscriptions à votre insu.
  • Un propriétaire d'organisation peut toujours inviter des utilisateurs, même inscriptions fermées. Pour bloquer aussi les invitations, c'est un réglage séparé.

Le /data réel après quelques minutes d'usage contient exactement ceci :

db.sqlite3 db.sqlite3-shm db.sqlite3-wal rsa_key.pem tmp/

Tout l'état (entrées, utilisateurs, organisations) est dans la base SQLite, et la clé rsa_key.pem signe les jetons d'authentification. Deux règles tirées des galères réelles de la communauté :

  • Ne copiez jamais db.sqlite3 à chaud. Les fichiers -wal et -shm provoquent une corruption. Utilisez VACUUM INTO (ou la commande .backup de SQLite) sur une copie cohérente, et avant une restauration, supprimez tout -wal/-shm périmé.
  • N'oubliez pas les pièces jointes. Une restauration de la seule base recrée les entrées mais casse les pièces jointes : sauvegardez tout /data, dont le dossier attachments/ (créé dès la première pièce jointe) et config.json (créé si la page admin a servi).

Ne placez pas /data sur un partage réseau (NFS, SMB) : SQLite y est sujet à corruption. Appliquez une politique 3-2-1. Après restauration, prévoyez de reconnecter chaque client et de re-saisir les 2FA (les sessions sont invalidées).

Si le serveur est exposé sur Internet, fail2ban bannit les attaques par force brute. Vaultwarden journalise les échecs (EXTENDED_LOGGING=true par défaut, avec un LOG_FILE), ce qui permet ce filtre :

[Definition]
failregex = ^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$

Deux pièges classiques :

  • L'IP réelle. Derrière un reverse proxy, le log enregistre 127.0.0.1 au lieu de l'IP de l'attaquant : fail2ban bannit le proxy, pas l'attaquant. Transmettez l'IP réelle via en-tête (X-Real-IP, ou CF-Connecting-IP derrière Cloudflare).
  • Docker et netfilter. Avec Vaultwarden en conteneur, les règles de ban peuvent atterrir dans une chaîne que le routage réseau Docker ne traverse pas : fail2ban logge le ban mais l'attaquant passe quand même. C'est un problème d'architecture firewall, pas de regex.

Question de fond : faut-il exposer le coffre sur Internet ? Beaucoup d'auto-hébergeurs ne l'exposent pas et y accèdent par VPN (WireGuard, Tailscale), au prix d'une synchronisation indisponible hors du réseau. Pensez aussi à la segmentation réseau : un appareil déjà compromis sur le LAN peut atteindre le coffre sans faille exposée sur Internet.

Vaultwarden corrige régulièrement des failles. Début 2026, plusieurs advisories de sévérité élevée ont été publiés (contournement de protection anti-force-brute, problèmes liés au SSO, SSRF via l'endpoint d'icônes), corrigés dans les versions récentes dont la 1.36.0. Épinglez une version à jour et suivez les advisories de sécurité du projet. N'inventez pas de numéro CVE : certains advisories sont publiés sans CVE assigné.

SymptômeCause probableCorrection
Coffre web bloqué sur l'écran de chargementAccès en HTTP simple (pas de contexte sécurisé)Passer en HTTPS (reverse proxy) ou tester en http://127.0.0.1 local
Login admin échoue après config du token$ non échappés dans le composeDoubler chaque $ en $$ dans le YAML
Notifications temps réel absentesEn-têtes WebSocket non transmisConfigurer le proxy pour passer Upgrade/Connection
fail2ban ne bannit personne d'utileIP réelle non transmise (proxy)Configurer X-Real-IP / CF-Connecting-IP
Base corrompue après sauvegardeCopie à chaud avec -wal/-shmUtiliser VACUUM INTO, purger les -wal périmés
  • Vaultwarden est un serveur Bitwarden-compatible léger, non officiel : parfait pour le homelab, pas pour un usage régulé.
  • HTTPS est obligatoire (contexte sécurisé des navigateurs) : un reverse proxy comme Caddy est la voie la plus simple.
  • Le panneau admin se protège par un ADMIN_TOKEN argon2, avec les $ doublés en Compose, et se désactive après usage.
  • Fermez les inscriptions (SIGNUPS_ALLOWED: false) et méfiez-vous de la whitelist et des invitations.
  • Sauvegardez tout /data (jamais la SQLite à chaud, jamais les pièces jointes oubliées), hors partage réseau.
  • Épinglez une version récente et suivez les advisories de sécurité.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn