Skip to content
Learni
Voir tous les tutoriels
Sécurité

Comment déployer Bitwarden self-hosted en 2026

Read in English

Introduction

Bitwarden est le gestionnaire de mots de passe open-source leader, mais son instance cloud pose des questions de souveraineté des données. En 2026, Vaultwarden (fork léger et performant de Bitwarden) domine les déploiements self-hosted grâce à sa compatibilité API totale et sa consommation ressources minimale (SQLite natif, <100MB RAM). Ce tutoriel advanced vous guide pour un déploiement production-ready : Docker Compose, reverse proxy SSL avec Traefik, authentification LDAP/SAML, backups chiffrés et automatisations API.

Pourquoi c'est crucial ? 80% des breaches proviennent de mots de passe faibles (Verizon DBIR 2025). Self-hostez pour un contrôle total, audits conformes RGPD et coûts <10€/mois. On part des bases Docker vers des configs enterprise comme monitoring Prometheus et scaling horizontal. Résultat : une instance haute-dispo accessible via apps mobiles/desktop Bitwarden officielles. Temps estimé : 45min. (128 mots)

Prérequis

  • Serveur Linux (Ubuntu 24.04+ ou VPS comme Hetzner/DigitalOcean, 2 vCPU/4GB RAM)
  • Docker 27+ et Docker Compose 2.29+
  • Domaine avec DNS A/AAAA pointant vers l'IP serveur
  • Certificat SSL wildcard (Let's Encrypt via Traefik)
  • Connaissances Docker, YAML et bash (niveau advanced)
  • Ports 80/443 ouverts (firewall UFW/AWS SG)

1. Structure du projet et .env

setup.sh
#!/bin/bash
mkdir -p ~/bitwarden/{data,backups,logs}
cd ~/bitwarden
cat > .env << EOF
DOMAIN=bitwarden.votredomaine.com
EMAIL=admin@votredomaine.com
TZ=Europe/Paris
BITWARDEN_ADMIN_TOKEN=supersecretadmintoken1234567890abcdef
WEBSOCKET_ENABLED=true
SIGNUPS_ALLOWED=false
INVITATIONS_ALLOWED=false
DISABLE_USER_REGISTRATION=true
EOF
chmod 600 .env

Ce script initialise la arborescence, génère un .env sécurisé avec variables critiques (token admin généré via openssl rand -base64 48). DOMAIN pour Traefik/SSL, SIGNUPS_ALLOWED=false pour prod. Piège : token faible = risque takeover ; utilisez pwgen ou 1Password pour génération.

Configuration initiale

Exécutez bash setup.sh dans un répertoire dédié. Le .env centralise les secrets : ne commitez jamais en Git. ADMIN_TOKEN sert pour l'interface /admin post-déploiement. TZ évite les drifts logs. On passe au docker-compose pour orchestrer Vaultwarden + Traefik.

2. Docker Compose avec Vaultwarden + Traefik

docker-compose.yml
version: '3.8'
services:
  traefik:
    image: traefik:v3.0
    restart: unless-stopped
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml:ro
      - ./acme.json:/acme.json
    environment:
      - TZ=${TZ}
    networks:
      - vaultwarden

  vaultwarden:
    image: vaultwarden/server:latest
    restart: unless-stopped
    volumes:
      - ./data/:/data/
    environment:
      - DOMAIN=https://${DOMAIN}
      - WEBSOCKET_ENABLED=${WEBSOCKET_ENABLED}
      - SIGNUPS_ALLOWED=${SIGNUPS_ALLOWED}
      - ADMIN_TOKEN=${BITWARDEN_ADMIN_TOKEN}
      - TZ=${TZ}
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.vaultwarden.rule=Host(`$${DOMAIN}`)"
      - "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt"
      - "traefik.http.routers.vaultwarden.entrypoints=websecure"
      - "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
    networks:
      - vaultwarden

networks:
  vaultwarden:
    driver: bridge

Ce compose.yml déploie Vaultwarden derrière Traefik pour SSL auto (Let's Encrypt). Labels Traefik routent HTTPS://DOMAIN vers port 80 interne. Volumes persistant data SQLite. Piège : sans WEBSOCKET_ENABLED, synchro temps réel (apps mobiles) échoue ; testez avec docker compose up -d.

3. Config Traefik statique

traefik.yml
global:
  checkNewVersion: false
  sendAnonymousUsage: false

entryPoints:
  web:
    address: ':80'
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanente: true
  websecure:
    address: ':443'

providers:
  docker:
    exposedByDefault: false

certificatesResolvers:
  letsencrypt:
    acme:
      email: ${EMAIL}
      storage: acme.json
      httpChallenge:
        entryPoint: web

traefik.yml configure entrypoints HTTP/HTTPS avec redirect auto et ACME Let's Encrypt. acme.json (chmod 600 pré-setup) stocke certs. Piège : sans exposedByDefault: false, tous conteneurs exposés = faille sécurité ; redémarrez Traefik après création acme.json vide.

Premier démarrage et admin

Créez touch acme.json && chmod 600 acme.json. Lancez docker compose up -d. Vérifiez logs : docker compose logs -f traefik. Accédez https://bitwarden.votredomaine.com. Créez compte admin via /admin avec token. Interface : activez 2FA TOTP/YubiKey.

4. Backup automatisé chiffré

backup.sh
#!/bin/bash
set -e
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR=~/bitwarden/backups
GPG_KEY=yourgpgkeyid
DATA_DIR=~/bitwarden/data
tar czf /tmp/vaultwarden-${DATE}.tar.gz -C ${DATA_DIR}/..
 gpg --batch --yes --encrypt --recipient ${GPG_KEY} --output ${BACKUP_DIR}/vaultwarden-${DATE}.tar.gz.gpg /tmp/vaultwarden-${DATE}.tar.gz
rm /tmp/vaultwarden-${DATE}.tar.gz
find ${BACKUP_DIR} -type f -mtime +30 -delete

# Cron: 0 2 * * * /path/to/backup.sh

Script daily backup data/ chiffré GPG, rotation 30j. set -e stoppe sur erreur. Piège : sans GPG_KEY (gpg --gen-key pré-setup), backup plaintext = risque ; testez bash backup.sh et gpg --decrypt file.gpg.

Intégrations enterprise

LDAP/SAML : Ajoutez env LDAP_SERVER=ldap://dc.example.com dans .env, relancez. Scaling : Ajoutez Redis pour HA (REDIS_URL=redis://redis:6379). Vérifiez /admin → Settings.

5. Monitoring Prometheus + Grafana

docker-compose.monitoring.yml
version: '3.8'
services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
    ports:
      - '9090:9090'

  grafana:
    image: grafana/grafana:latest
    ports:
      - '3000:3000'
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123

  vaultwarden-exporter:
    image: ghcr.io/wystia/vaultwarden-exporter:latest
    command: --vaultwarden-url=http://vaultwarden:80
    ports:
      - '8080:8080'

Extension monitoring : exporter métriques Vaultwarden (logins, vaults). prometheus.yml à créer avec scrape vaultwarden-exporter. Piège : sans dashboard Grafana import 1860 (Bitwarden), métriques inutiles ; accédez Grafana:3000.

6. Script API : Lister vaults (Node.js)

api-vaults.ts
import fetch from 'node-fetch';

const BW_URL = 'https://bitwarden.votredomaine.com';
const BW_EMAIL = 'admin@example.com';
const BW_PASSWORD = 'strongpassword123';
const BW_CLIENT_ID = 'yourclientid';
const BW_CLIENT_SECRET = 'yourclientsecret';

async function login() {
  const response = await fetch(`${BW_URL}/identity/connect/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'password',
      username: BW_EMAIL,
      password: BW_PASSWORD,
      scope: 'api',
      client_id: BW_CLIENT_ID,
      client_secret: BW_CLIENT_SECRET,
    }),
  });
  const data = await response.json();
  return data.access_token;
}

async function listVaults(token: string) {
  const response = await fetch(`${BW_URL}/api/accounts`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  const accounts = await response.json();
  console.log('Vaults:', accounts.data);
}

(async () => {
  const token = await login();
  await listVaults(token);
})();

Exemple TS pour API Bitwarden : login OAuth, liste comptes/vaults. Créez app /admin → API → Client ID/Secret. npm i node-fetch. Piège : scope 'api' obligatoire ; token expire 5min, refresh via refresh_token.

Bonnes pratiques

  • Secrets rotation : Changez ADMIN_TOKEN/PW mensuel via /admin → Rotate.
  • HA setup : 3 nodes Vaultwarden + Postgres (env DATABASE_URL=postgres://...) + Redis.
  • Audit logs : Activez LOG_FILE=/data/logs, ship vers ELK/Fluentd.
  • Zero Trust : Fail2ban sur logs Traefik + WAF Cloudflare.
  • MFA enforced : /admin → Policies → Require 2FA pour tous users.

Erreurs courantes à éviter

  • Certs non persistant : Oubli acme.json = downtime SSL à chaque restart.
  • SQLite en prod : >100 users → migrez Postgres (docker exec vaultwarden sqlite3 /data/db.sqlite3 .dump | psql).
  • Websockets ignorés : Apps déconnectées ; vérifiez DOMAIN=https:// et port 3012 exposé si besoin.
  • Pas de backups : Perte data irréversible ; testez restore weekly.

Pour aller plus loin

Explorez docs Vaultwarden, Bitwarden API. Intégrez avec Authentik pour SSO. Découvrez nos formations Learni sur DevSecOps pour masterclass self-hosting sécurisé.