Vaultwarden est-il sécurisé ? Vérifiez ces risques
Vaultwarden chiffre chaque élément du coffre côté client : le serveur ne voit aucun texte en clair. Protégez surtout le token admin et vos sauvegardes.
Vaultwarden est-il sécurisé ? Réponse courte
Vaultwarden est sécurisé à l’endroit le plus important : chaque élément du coffre est chiffré sur votre appareil avant d’atteindre le serveur. Le serveur stocke des blobs qu’il ne peut pas lire. Même une personne qui copie toute la base de données a encore besoin du mot de passe maître pour en tirer quoi que ce soit d’utile.
Cette réponse repose sur plusieurs conditions, et les problèmes viennent de la configuration. Un panneau d’administration protégé par un token facile à deviner. Un port de conteneur publié sur l’ensemble d’Internet. Un config.json en clair. Une archive tar de sauvegarde stockée dans le répertoire personnel sur le même serveur. Aucun de ces problèmes ne concerne la cryptographie. Ce sont précisément les raisons pour lesquelles des coffres auto-hébergés sont vidés.
La suite suppose que l’installation fonctionne. Si ce n’est pas encore le cas, commencez par l’installer en suivant le guide d’installation de Vaultwarden sur un VPS, puis revenez ici et suivez cette liste dans l’ordre.
Ce que le serveur stocke réellement
Vaultwarden implémente le modèle de données de Bitwarden. Le nom, le nom d’utilisateur, le mot de passe, les notes et les URI d’un élément du coffre sont chiffrés avec une clé dérivée de votre mot de passe principal, sur le client, avant l’envoi de toute requête. Le contenu des fichiers joints est chiffré de la même manière. Le serveur reçoit des données opaques auxquelles est associé un UUID (identifiant universel unique).
Certaines données ne sont pas chiffrées. Vous devez savoir précisément lesquelles :
- L’adresse e-mail de votre compte, en clair.
- Les paramètres et le sel de votre KDF (fonction de dérivation de clé), car le client en a besoin pour reconstruire la clé lors de la prochaine connexion.
- Un hash côté serveur du hash du mot de passe principal envoyé par le client, utilisé pour authentifier la connexion elle-même.
- Les métadonnées : appartenance à une organisation, noms des appareils, heures de dernière connexion.
- Le secret de la méthode à deux facteurs qui protège la connexion à Vaultwarden. Il se trouve sans chiffrement dans la table
twofactor, car le serveur doit calculer le code attendu pour le comparer au vôtre. Il ne s’agit pas du même secret TOTP (mot de passe à usage unique basé sur le temps) que celui que vous stockez dans un élément du coffre, qui est chiffré comme n’importe quel autre champ.
Le dossier de données est petit. Avec une installation Docker, il s’agit du chemin que vous avez monté sur /data.
sudo ls -l /vw-data/db.sqlite3 contient presque tout l’état. attachments/ contient les fichiers téléversés, un par UUID, et c’est la seule catégorie importante de données qui ne se trouve pas dans les tables de la base de données. sends/ contient les pièces jointes de Send et est destiné aux données temporaires. icon_cache/ peut être supprimé sans conséquence. rsa_key.pem et les fichiers associés signent les JWT (JSON Web Tokens) des utilisateurs connectés. Une copie de cette clé privée peut donc servir à falsifier une session de connexion au coffre. config.json n’existe qu’après l’activation de la page d’administration. Le projet est explicite à ce sujet : ce fichier contient le token d’administration et vos identifiants SMTP en clair.
En pratique, le modèle de menace concerne l’accès au système de fichiers, et non la cryptographie réseau. Un accès en lecture à ce seul répertoire donne accès à l’adresse e-mail de chaque utilisateur, à ses secrets 2FA de connexion, à une clé permettant de falsifier des sessions et à une copie hors ligne de chaque coffre, qui peut être attaquée ultérieurement. Toutes les étapes ci-dessous visent à empêcher l’accès à ce répertoire.
Corrigez d’abord le token d’administration
/admin est un panneau d’administration complet : liste des utilisateurs, invitations, suppression et accès à tous les paramètres d’exécution. Il est protégé par un seul secret partagé, sans autre protection. Il n’y a pas de nom d’utilisateur ni d’authentification à deux facteurs par utilisateur.
Les anciens guides vous indiquent de générer ADMIN_TOKEN avec openssl rand -base64 48. Cette méthode fonctionne et écrit le secret en clair dans config.json ainsi que dans votre fichier Compose. Vaultwarden accepte également une chaîne PHC Argon2 (password hashing competition), ce qui permet de stocker un hash à la place du secret. Générez-en une avec un conteneur en cours d’exécution :
docker exec -it vaultwarden /vaultwarden hashOu sans toucher au conteneur en cours d’exécution :
docker run --rm -it vaultwarden/server /vaultwarden hashLa commande demande le mot de passe deux fois, puis affiche une ligne qui commence par $argon2id$. Sur une installation bare metal, exécutez ./vaultwarden hash. Si vous préférez utiliser directement la CLI argon2, le projet documente les paramètres minimaux recommandés par l’OWASP :
echo -n 'MySecretPassword' | argon2 "$(openssl rand -base64 32)" -e -id -k 19456 -t 2 -p 1Voici le piège qui peut vous faire perdre une heure. Une chaîne PHC contient de nombreux caractères $, et Docker Compose traite $ comme une interpolation de variable. Si vous la collez sans l’échapper dans un bloc environment:, la valeur transmise au conteneur est altérée. /admin rejette alors un token pourtant correct. Deux méthodes sont sûres. Dans docker-compose.yml, doublez chaque $ :
environment:
ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$UUZxK1FZMkZoRHFQRlVrTXZvS0E3bHpNQW55c2dBN2NORzdsa0Nxd1JhND0$$cUoId+JBUsJutlG4rfDZayExfjq4TCt48aBc9qsc3UIDans un fichier .env, aucun échappement n’est nécessaire, mais utilisez des guillemets simples :
ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'Limitez ensuite le débit du panneau et réduisez la durée de sa session :
ADMIN_RATELIMIT_SECONDS=300
ADMIN_RATELIMIT_MAX_BURST=3
ADMIN_SESSION_LIFETIME=20Après trois tentatives incorrectes en cinq minutes, le panneau cesse de répondre à ce client. La session d’administration expire après 20 minutes d’inactivité.
Mieux encore : désactivez cette page. La plupart des installations n’en ont besoin qu’une seule fois, pour configurer SMTP et inviter les premiers utilisateurs. Pour la désactiver, ne définissez ni ADMIN_TOKEN ni DISABLE_ADMIN_TOKEN, supprimez toute clé "admin_token" de config.json, puis recréez le conteneur. Il est important de supprimer la clé du fichier, car la page d’administration y écrit les paramètres et la valeur présente dans config.json est prioritaire sur la variable d’environnement. Supprimer uniquement la variable laisse la page accessible.
Fermer les inscriptions avant que quelqu’un ne trouve le domaine
SIGNUPS_ALLOWED=false
SIGNUPS_VERIFY=true
SHOW_PASSWORD_HINT=falseSIGNUPS_ALLOWED est défini sur true par défaut. Laissez cette valeur : toute personne qui accède à votre domaine peut alors créer un compte, et ses données sont stockées dans le même db.sqlite3 que les vôtres. Définissez-le sur false et ajoutez les utilisateurs au moyen d’invitations envoyées depuis la page d’administration. Cette fonction nécessite un serveur SMTP opérationnel. INVITATIONS_ALLOWED est également défini sur true par défaut et permet aux propriétaires d’une organisation d’inviter d’autres utilisateurs. Cela convient si vous faites confiance à vos utilisateurs. Sur une instance utilisée par une seule personne, cette option doit être définie sur false. Si seuls certains domaines doivent pouvoir s’inscrire, SIGNUPS_DOMAINS_WHITELIST=example.com est plus restrictif que les inscriptions ouvertes, mais nettement moins sûr que les invitations.
SHOW_PASSWORD_HINT est défini sur false par défaut et doit le rester. Lorsqu’elle est activée, la saisie d’une adresse e-mail valide dans le formulaire de connexion renvoie l’indice du mot de passe maître du compte correspondant. Cela divulgue l’indice et confirme que l’adresse existe.
Si votre instance a autorisé les inscriptions ouvertes pendant un certain temps, ouvrez la page d’administration et consultez la liste des utilisateurs avant de supposer que vous êtes la seule personne à y avoir un compte.
Le port que vous ne vouliez pas publier
L’image Docker écoute sur le port 80 dans le conteneur. Une installation bare metal utilise par défaut ROCKET_PORT=8000. La commande documentée publie ce port comme suit :
--publish 127.0.0.1:8000:80Le préfixe 127.0.0.1: est l’élément essentiel. Si vous écrivez plutôt -p 8000:80, Docker lie 0.0.0.0 et ajoute pour cela des règles DNAT (destination network address translation) dans la table nat. Ces règles sont évaluées avant les chaînes filter gérées par ufw. Ainsi, ufw status indique que le port est refusé alors que celui-ci répond bien sur Internet. Le fonctionnement complet est expliqué dans le guide sur les ports Docker qui contournent ufw.
Vérifiez ce qui écoute réellement :
sudo ss -tlnp | grep 8000Le résultat attendu est une seule ligne liée à 127.0.0.1:8000. Une ligne liée à 0.0.0.0:8000 signifie que le coffre-fort est directement exposé. Corrigez le mapping, puis recréez le conteneur, car une liaison de port est fixée lors de la création du conteneur et docker compose restart ne la modifiera pas :
docker compose up -d --force-recreateUn autre port apparaît encore dans d’anciens guides : 3012, le port WebSocket séparé. Sa prise en charge a été supprimée dans Vaultwarden 1.31.0, car le trafic de notification utilise désormais le port HTTP principal. WEBSOCKET_ENABLED et WEBSOCKET_PORT sont ignorés depuis la version 1.29.0. Le paramètre actuel est ENABLE_WEBSOCKET, dont la valeur par défaut est true. Si votre pare-feu ou votre fichier compose ouvre encore le port 3012, fermez-le.
Terminez TLS sur un reverse proxy, pas dans Rocket
Vaultwarden peut gérer lui-même TLS (transport layer security) via Rocket, son framework web. Le projet déconseille cette configuration en production. Le TLS intégré de Rocket ne prend pas correctement en charge le SNI (server name indication), ce qui explique aussi pourquoi les recommandations de hardening demandent d’accéder à votre instance par son hostname et jamais par une adresse IP seule. Les plages d’adresses IP publiques sont analysées en permanence. Un coffre-fort qui répond sur une adresse IP finit par être découvert.
Les éléments importants d’un server block nginx :
client_max_body_size 525M;
location / {
proxy_pass http://127.0.0.1:8000;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}nginx limite par défaut client_max_body_size à 1 MB. Sans cette ligne, l’envoi d’une pièce jointe échoue avec 413 Request Entity Too Large dans le journal d’erreurs nginx, tandis que Vaultwarden n’enregistre absolument rien. Les en-têtes Upgrade et Connection transmettent le handshake WebSocket à /notifications/hub. Si vous les supprimez, le coffre-fort continue de fonctionner, mais les modifications cessent d’apparaître sur vos autres appareils jusqu’à ce que vous rechargiez manuellement la page.
La configuration Caddy est plus courte et obtient lui-même le certificat :
vw.example.com {
reverse_proxy 127.0.0.1:8000 {
header_up X-Real-IP {remote_host}
}
}Indiquez ensuite cette configuration à Vaultwarden :
DOMAIN=https://vw.example.com
IP_HEADER=X-Real-IPIP_HEADER vaut déjà X-Real-IP par défaut. Il faut donc vérifier que le proxy définit effectivement cet en-tête. Sinon, chaque ligne de journal et chaque limite de débit de connexion voient 127.0.0.1, c’est-à-dire le proxy lui-même. Les échecs d’un seul attaquant sont alors comptabilisés pour tous les utilisateurs de l’instance. Définissez également DOMAIN sur la véritable URL https, car Vaultwarden construit à partir de cette valeur les liens d’invitation et de réinitialisation du mot de passe. Les clés de sécurité WebAuthn sont aussi liées à cette origine.
Un détail est souvent oublié : la connexion WebSocket transmet le jeton de session dans la query string, sous la forme /notifications/hub?access_token=[JWT]. Celui-ci apparaît en clair dans le journal d’accès du proxy. Masquez le paramètre access_token dans le format des journaux, ou vérifiez que ces journaux ne sont pas envoyés vers un emplacement que vous ne contrôlez pas.
Interdire le brute force sur le point de connexion
Les limites de débit sont activées par défaut (LOGIN_RATELIMIT_SECONDS=60, LOGIN_RATELIMIT_MAX_BURST=10). Elles ralentissent l’attaquant. Elles ne l’arrêtent pas. fail2ban le fait, mais Vaultwarden doit d’abord écrire un fichier journal, ce qu’il ne fait pas par défaut :
LOG_FILE=/data/vaultwarden.log
LOG_LEVEL=info
EXTENDED_LOGGING=trueUne tentative de connexion échouée produit alors exactement une ligne. C’est cette chaîne que votre filtre doit rechercher :
[2026-08-07 09:14:22][vaultwarden::api::identity][ERROR] Username or password is incorrect. Try again. IP: 203.0.113.10. Username: user@example.com.Écrivez le filtre dans /etc/fail2ban/filter.d/vaultwarden.local :
[INCLUDES]
before = common.conf
[Definition]
failregex = ^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =Puis la jail dans /etc/fail2ban/jail.d/vaultwarden.local :
[vaultwarden]
enabled = true
port = 80,443
filter = vaultwarden
banaction = %(banaction_allports)s
logpath = /vw-data/vaultwarden.log
maxretry = 3
bantime = 14400
findtime = 14400Si vous avez conservé la page d’administration, ajoutez une seconde jail dont le failregex est ^.*Invalid admin token\. IP: <ADDR>.*$, car les échecs d’administration sont consignés avec un message différent et le filtre de connexion ne les détectera jamais. Vérifiez ensuite votre configuration :
sudo systemctl restart fail2ban
sudo fail2ban-client status vaultwardenUne jail fonctionnelle indique votre fichier journal dans File list et affiche Currently failed: 0. Saisissez trois fois un mot de passe incorrect depuis un autre réseau : ce compteur augmente, puis l’adresse apparaît dans Banned IP list. Si le compteur ne bouge jamais, la cause la plus fréquente est logpath : il doit s’agir du chemin du fichier sur l’hôte, et non du chemin /data/... à l’intérieur du conteneur. L’autre cause fréquente est l’absence de X-Real-IP, ce qui fait que chaque bannissement cible votre propre proxy. Le reste de la configuration, notamment la jail SSH que vous devriez déjà utiliser, est expliqué dans le guide fail2ban pour Ubuntu 24.04.
Le mot de passe maître reste le point central du système
Le chiffrement côté client signifie que le mot de passe maître sert de clé. Un mot de passe maître court sur une instance dont la base de données a été copiée par un attaquant n’est protégé par rien dans cet article, car l’attaquant peut effectuer une attaque hors ligne sur cette copie, à la vitesse permise par son matériel. Aucun réglage du serveur ne s’applique à la machine de l’attaquant.
PASSWORD_ITERATIONS=600000 correspond au nombre d’itérations du KDF transmis aux clients lors de la création d’un compte. Les comptes existants conservent la valeur utilisée lors de leur création. L’augmenter ne change donc rien pour les utilisateurs inscrits l’année dernière. Ils doivent modifier eux-mêmes cette valeur dans les paramètres de sécurité du coffre-fort web, ce qui rechiffre leur clé. Informez-les, car l’interface ne l’indiquera pas.
Activez ensuite l’authentification à deux facteurs pour chaque compte. Elle ne protège pas le texte chiffré, car la clé du coffre-fort provient uniquement du mot de passe maître. En revanche, elle empêche qu’un mot de passe volé suffise pour se connecter et synchroniser une copie. REQUIRE_DEVICE_EMAIL=true ajoute une étape de confirmation par e-mail lors de la première connexion d’un compte depuis un appareil non reconnu.
Les sauvegardes sont le point faible des coffres auto-hébergés
Une tar czf du dossier de données, laissée dans un répertoire personnel sur le même VPS, annule toutes les étapes précédentes. Cette archive contient db.sqlite3 avec le texte chiffré de chaque utilisateur, rsa_key.pem qui forge les sessions de connexion, et config.json avec le token d’administration et le mot de passe SMTP en clair. L’accès en lecture à ce fichier suffit pour accéder au coffre.
Deux règles suffisent. Sortez l’archive du serveur. Chiffrez-la avant de la transférer.
Il y a aussi un problème d’intégrité. Copier db.sqlite3 avec cp pendant que le service fonctionne peut produire un fichier en cours d’écriture qui ne s’ouvrira pas. Vous ne le découvrirez qu’au moment de la restauration. Utilisez plutôt le snapshot natif de SQLite :
sqlite3 /vw-data/db.sqlite3 ".backup '/tmp/vw-db-backup.sqlite3'"La restauration, que presque personne ne teste, est couverte dans le guide de sauvegarde et de restauration de Vaultwarden.
Ce que vous abandonnez par rapport à Bitwarden hébergé
Bilan honnête. Le service hébergé de Bitwarden est exploité par des personnes dont le métier à temps plein consiste à l’administrer. Il fait l’objet d’audits tiers publiés et quelqu’un est d’astreinte à 3 heures du matin. L’auto-hébergement remplace cela par votre propre rythme d’application des correctifs.
Vaultwarden publie les correctifs de sécurité dans le cadre de releases ordinaires. La version 1.37.0, publiée le 24 July 2026, est la version actuelle en August 2026, et ses notes demandent aux utilisateurs de mettre à jour dès que possible. Une instance configurée il y a un an puis oubliée exécute du code vieux d’un an. Le tag latest ne suffit pas à lui seul : un conteneur en cours d’exécution conserve l’image avec laquelle il a démarré tant que vous n’exécutez pas docker compose pull et que vous ne le recréez pas. Mettez en place des mises à niveau automatiques sur Ubuntu pour les paquets de l’hôte, et inscrivez la mise à jour du conteneur dans un rappel de calendrier que vous lirez réellement.
La conclusion qu’un lecteur honnête devrait tirer est la suivante : la cryptographie repose ici sur la conception de Bitwarden et elle reste solide, tandis que le risque opérationnel vous incombe entièrement. Si vous appliquez les correctifs et sauvegardez les données ailleurs, une instance Vaultwarden sur un VPS que vous contrôlez constitue un emplacement raisonnable pour conserver vos mots de passe. Si ces deux habitudes ne seront pas suivies, payez le service hébergé et consacrez votre attention à autre chose. La comparaison fonctionnalité par fonctionnalité se trouve dans Vaultwarden comparé à Bitwarden auto-hébergé.
Renforcez l’hôte sous le conteneur
Vaultwarden est un processus sur une machine Linux. Le compte root de cette machine peut lire /vw-data, quelle que soit la configuration de l’application. Exécutez le conteneur avec un utilisateur non privilégié à l’aide de user: "1000:1000" dans votre fichier Compose, en attribuant les droits appropriés au dossier de données. Montez ensuite en lecture seule tout ce que le conteneur n’a pas besoin de modifier avec :ro. Fermez enfin la porte d’entrée : Renforcer la sécurité de SSH sur un VPS explique comment autoriser uniquement la connexion par clé et désactiver l’authentification par mot de passe. Cela bloque l’attaque classique qui permettrait de contourner toutes les mesures précédentes.
FAQ
Un tiers peut-il lire mes mots de passe s’il vole la base de données Vaultwarden ?
Pas directement. Chaque élément du coffre est chiffré côté client avec une clé dérivée du mot de passe maître. db.sqlite3 contient donc du texte chiffré. L’attaquant obtient immédiatement l’adresse e-mail de chaque compte, les paramètres KDF, les métadonnées de connexion et d’appareil, ainsi que les secrets d’authentification à deux facteurs de la table twofactor. Ces derniers sont stockés en clair, car le serveur doit calculer le code attendu. L’attaquant peut aussi tenter hors ligne de casser le texte chiffré du coffre aussi longtemps qu’il le souhaite. C’est pourquoi la longueur du mot de passe maître détermine l’issue.
Dois-je utiliser ADMIN_TOKEN ou désactiver complètement la page d’administration ?
Désactivez-la si possible. La plupart des instances en ont besoin une seule fois pour configurer SMTP et inviter des utilisateurs, puis plus jamais. Pour la désactiver, ne définissez ni ADMIN_TOKEN ni DISABLE_ADMIN_TOKEN, supprimez toute clé "admin_token" de config.json, puis recréez le conteneur. Supprimer uniquement la variable d’environnement ne suffit pas, car les paramètres enregistrés par la page d’administration résident dans config.json et sont prioritaires. Si vous conservez la page, stockez le token sous forme de hash Argon2 produit par vaultwarden hash plutôt que sous forme de chaîne aléatoire en clair, puis définissez ADMIN_RATELIMIT_MAX_BURST=3.
Mon ADMIN_TOKEN est correct, mais /admin le refuse. Quel est le problème ?
Il s’agit presque toujours d’une interpolation $. Une chaîne PHC Argon2 contient plusieurs caractères $. Docker Compose les interprète comme des variables à l’intérieur d’un bloc docker-compose.yml environment:. Le conteneur reçoit donc une valeur altérée, alors que votre fichier semble correct. Doublez chaque $ en $$ dans le fichier Compose, ou placez la valeur dans un fichier .env entourée de guillemets simples. Aucun échappement n’est alors nécessaire. Recréez ensuite le conteneur, car un redémarrage ne recharge pas les modifications de l’environnement.
Dois-je encore ouvrir le port 3012 pour les notifications ?
Non. La prise en charge du trafic WebSocket sur le port 3012 a été supprimée dans Vaultwarden 1.31.0, car les notifications utilisent désormais le port HTTP principal. WEBSOCKET_ENABLED et WEBSOCKET_PORT sont ignorés depuis la version 1.29.0. Le paramètre actuel est ENABLE_WEBSOCKET, avec true comme valeur par défaut. Fermez le port 3012 dans le firewall et supprimez-le de votre fichier Compose. Vérifiez ensuite que votre reverse proxy transmet les en-têtes Upgrade et Connection, car la synchronisation en temps réel en dépend désormais.