SSD Nodes Learn
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-07-24

Vaultwarden op VPS installeren met Docker

Leer Vaultwarden zelf hosten op een VPS met Docker. Wij bespreken de configuratie van TLS, het uitschakelen van registraties en het maken van veilige backups.

Wat u bouwt

Een wachtwoordbeheerder die volledig van u is: Vaultwarden draaiend in één kleine container achter een reverse proxy die HTTPS afhandelt. U gebruikt de officiële Bitwarden apps op uw telefoon, laptop en browser om verbinding te maken. Vaultwarden implementeert de Bitwarden server API opnieuw in Rust. Het gebruikt hetzelfde protocol als bitwarden.com, waardoor elke officiële client zonder wijzigingen werkt. Het verbruikt slechts ongeveer 100 MB RAM, in tegenstelling tot de officiële stack met meerdere containers.

De installatie bestaat uit een dozijn regels Compose. De drie zaken die cruciaal zijn — en die vaak misgaan — zijn: TLS moet actief zijn voordat u de web vault laadt, publieke registraties moeten worden uitgeschakeld zodra uw eigen account bestaat, en het datavolume moet worden gebackupt en getest via een restore. Deze ene directory bevat namelijk al uw wachtwoorden.

Vereisten en belangrijke aandachtspunten

  • Een VPS met Docker Engine en de Compose plugin, op een schone Ubuntu 24.04 KVM-server met root- of sudo-rechten. 512 MB RAM is voldoende; 1 GB is comfortabel. Dit is een van de lichtste applicaties die u kunt draaien — het staat bovenaan de lijst met services die het waard zijn om zelf te hosten.
  • Een domein met een A-record (en AAAA als u IPv6 gebruikt) dat naar vault.example.com op de VPS wijst. Het TLS-certificaat wordt uitgegeven voor deze specifieke naam, dus DNS moet correct werken voordat u begint.
  • Poorten 80 en 443 moeten openstaan voor het internet en worden afgehandeld door uw reverse proxy — nooit direct door Vaultwarden. Poort 80 wordt uitsluitend gebruikt voor de ACME-certificaatuitdaging en een HTTP-naar-HTTPS redirect.
  • Het belangrijkste aandachtspunt vooraf: de Bitwarden-clients weigeren verbinding te maken met een server die geen HTTPS gebruikt. U kunt dit niet eerst via HTTP testen — die methode werkt niet om een specifieke reden die hierna wordt uitgelegd.

Waarom Vaultwarden, niet de officiële Bitwarden stack

Dezelfde clients, maar een fractie van de belasting. De officiële zelfgehoste Bitwarden wordt geleverd als een bundel containers (MSSQL, Nginx, Identity, Api, Admin en meer) en vereist ongeveer 2 GB RAM. Vaultwarden is een enkele binary die standaard alles in een SQLite database opslaat en slechts enkele tientallen megabytes verbruikt tijdens inactiviteit. Voor één persoon, een gezin of een klein team is dit de voor de hand liggende keuze. Omdat Vaultwarden de Bitwarden API nauwkeurig implementeert, blijven uw gegevens verplaatsbaar tussen Vaultwarden en bitwarden.com.

U verliest hiermee de meeste enterprise-functies: geen SCIM provisioning (hoewel experimentele OpenID Connect SSO is toegevoegd in 1.35.0). Daarnaast bent u zelf de beheerder, wat betekent dat het patchen, HTTPS en backups uw verantwoordelijkheid zijn. Deze handleiding behandelt deze drie taken.

Waarom HTTPS niet optioneel is

De Bitwarden web vault en browser-extensies genereren uw encryptiesleutels in de browser via de Web Crypto API (window.crypto.subtle). Browsers maken crypto.subtle alleen beschikbaar in een secure context — HTTPS, of het specifieke geval van http://localhost. Via gewone http://vault.example.com is dit undefined. Zodra de app een sleutel genereert, treedt er een fout op en toont de console:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'importKey')

De pagina blijft hangen of geeft een algemene crypto-fout weer, waardoor inloggen niet mogelijk is. De desktop-, mobiele- en browserclients voeren een eigen controle uit tegen een zelfgehoste URL. Bij een http-endpoint (of een onbereikbaar endpoint) weigert de client met de melding:

This is not a recognized Bitwarden server. You may need to check with your provider or update your server.

Beide situaties hebben dezelfde oorzaak: geen geldige HTTPS. Gebruik daarom eerst TLS en open de vault nooit via http, zelfs niet eenmalig voor een snelle controle.

Stap 1 — DNS en de reverse proxy (eerst TLS)

Wijs het record naar uw VPS en controleer of dit naar het juiste adres verwijst:

dig +short vault.example.com

De regel die wordt weergegeven moet het IP-adres van uw VPS zijn. Als deze leeg is of onjuist, herstel dan de DNS-instellingen en wacht de TTL af — het uitgeven van certificaten mislukt als een naam niet correct wordt opgelost.

Voor de HTTPS front end gebruikt deze handleiding Traefik. Traefik geeft Let's Encrypt-certificaten automatisch uit en vernieuwt deze, en integreert direct met Compose. Als u dit nog niet gebruikt, volg dan eerst de Traefik reverse proxy en automatische TLS setup; dit maakt een extern Docker-netwerk aan (proxy hieronder) en een ACME resolver (letsencrypt) waar de Vaultwarden-service zich aan koppelt. Een standaard nginx met een handmatig uitgegeven certificaat werkt identiek vanuit het perspectief van Vaultwarden.

Heeft u de voorkeur voor nginx en Certbot in plaats van Traefik? Plaats Vaultwarden op 127.0.0.1:8080 (voeg ports: ["127.0.0.1:8080:80"] toe aan de service en verwijder de Traefik labels), stel vervolgens een certificaat in en gebruik een proxy naar de service. Het onderdeel over certificaten wordt behandeld in het uitgeven van Let's Encrypt-certificaten met Certbot en nginx. De cruciale extra stap is de WebSocket upgrade op het notificatiepad:

server {
    listen 443 ssl;
    server_name vault.example.com;

    client_max_body_size 525M;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Let op de X-Real-IP regel — dit zorgt ervoor dat Fail2ban later de echte aanvaller ziet in plaats van 127.0.0.1. De rest van deze handleiding is identiek, ongeacht of Traefik of nginx als front end wordt gebruikt.

Stap 2 — het Compose-bestand

Maak eerst de projectmap aan. Deze handleiding gebruikt /opt/vaultwarden. Hierdoor is de Compose-projectnaam — en dus de data-volume vaultwarden_vw-data — voorspelbaar. De stappen voor Fail2ban en de backup hieronder zijn afhankelijk van deze exacte naam.

sudo mkdir -p /opt/vaultwarden
cd /opt/vaultwarden

Maak een .env aan voor de admin secret en plaats het Compose-bestand in die map.

# .env
ADMIN_TOKEN=paste-a-strong-token-here

Genereer de token met openssl rand -base64 48 en plak deze in het bestand. (Een sterkere hash-vorm wordt in de volgende stap behandeld; een lange willekeurige string is voldoende om mee te beginnen.)

# docker-compose.yml
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      DOMAIN: "https://vault.example.com"
      SIGNUPS_ALLOWED: "true"          # closed in Step 4, keep true just to register
      ADMIN_TOKEN: "${ADMIN_TOKEN}"
      IP_HEADER: "X-Forwarded-For"     # X-Real-IP if your proxy sends that instead
      LOG_FILE: "/data/vaultwarden.log"
      LOG_LEVEL: "warn"
    volumes:
      - vw-data:/data
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.vw.rule=Host(`vault.example.com`)"
      - "traefik.http.routers.vw.entrypoints=websecure"
      - "traefik.http.routers.vw.tls.certresolver=letsencrypt"
      - "traefik.http.services.vw.loadbalancer.server.port=80"

volumes:
  vw-data:

networks:
  proxy:
    external: true

Twee zaken in dit bestand zijn essentieel voor het ontwerp. Er is geen ports: mapping. Vaultwarden is hierdoor alleen bereikbaar via Traefik en de TLS daarvan. Het publiceren van de poort op de host zorgt ervoor dat gebruikers de vault per ongeluk via http gebruiken. Daarnaast moet DOMAIN de volledige publieke HTTPS-URL zijn. Deze URL is verwerkt in bijlage-links, WebAuthn 2FA en de notifications endpoint. Een foutieve waarde of een http-waarde zorgt ervoor dat deze functies niet werken, zelfs als de site wel laadt. De latest tag is een bewuste uitzondering op de gebruikelijke never-latest regel. Vaultwarden levert stabiele releases als een enkele rolling image, met :testing als het aparte pre-release kanaal. Update daarom bewust en lees de release notes voordat u de image pullt.

Start het systeem en bekijk het logboek:

docker compose up -d
docker compose logs -f vaultwarden

Een correcte start eindigt met een regel zoals Rocket has launched from http://0.0.0.0:80. Geef Traefik enkele seconden om het certificaat op te halen en laad vervolgens https://vault.example.com. U zou de Bitwarden web vault moeten zien met een geldig hangslot en zonder certificaatwaarschuwing.

Stap 3 — een sterke ADMIN_TOKEN en de $$ valstrik

ADMIN_TOKEN beschermt /admin, het paneel dat elke gebruiker en instelling op uw instance kan lezen. Behandel dit daarom als een root-wachtwoord. Twee methoden zijn mogelijk.

De eenvoudige methode is de willekeurige string die u al heeft gegenereerd met openssl rand -base64 48. Omdat base64 nooit een $ bevat, kan deze direct in .env worden geplaatst zonder escaping.

De beveiligde methode is een Argon2 PHC-hash, waardoor de plaintext token nooit op de schijf wordt opgeslagen. Genereer een hash met dezelfde image:

docker run --rm -it vaultwarden/server /vaultwarden hash --preset owasp

De opdracht vraagt twee keer om invoer en print een string die begint met $argon2id$v=19$.... Hier is de valstrik die gebruikers een uur kost: Docker Compose behandelt $ als variabele interpolatie. U moet daarom elke $ verdubbelen naar $$ wanneer u de hash in het Compose-bestand plakt. Plaats deze direct onder environment:, niet via .env, en gebruik geen aanhalingstekens:

    environment:
      ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$c29tZXNhbHQ$$RdescudvJCsgt3ub+b+dWRWJTmaaJObG

Als u de enkele $ tekens laat staan, geeft Compose een The "argon2id" variable is not set waarschuwing en wordt de token leeg gemaakt. Hierdoor weigert /admin vervolgens uw correcte wachtwoord. Voer docker compose up -d uit en bewaar de plaintext die u bij de prompt heeft ingevoerd in uw eigen wachtwoordbeheerder.

Stap 4 — registreer uw account, en sluit daarna de deur

Open met SIGNUPS_ALLOWED: "true" de https://vault.example.com, klik op Create account en registreer met uw e-mailadres en een sterk master password. Dit master password is nooit herstelbaar — er is geen reset-optie — dus sla het eerst veilig op.

Sluit nu de deur. Pas het Compose file aan om aanmeldingen uit te schakelen:

      SIGNUPS_ALLOWED: "false"

Pas de wijzigingen toe met docker compose up -d. Dit is geen hardening die u kunt uitstellen. Als de registratie open blijft staan, kan iedereen die de URL vindt — en crawlers doen dat — een account aanmaken op uw server. Zij kunnen uw vault niet lezen, maar ze verbruiken wel resources en maken van uw privé-instantie een openbare dienst. Het bewijs dat u de registratie open heeft laten staan: /admin bevat accounts die u nooit heeft aangemaakt.

Om later familie of teamleden toe te voegen zonder de publieke registratie te heropenen, gebruikt u de Invite User knop in /admin; voor deze methode moet SMTP zijn geconfigureerd zodat de ontvanger de link ontvangt.

Stap 5 — toegang tot /admin

Navigeer naar https://vault.example.com/admin en voer de plaintext admin token in (de willekeurige reeks, of het wachtwoord dat u heeft gehasht — niet de hash zelf). Binnen kunt u gebruikers opsommen, instellingen aanpassen, een test-email verzenden en een database snapshot maken.

Als de pagina 404 Not Found retourneert, is ADMIN_TOKEN leeg of niet ingesteld. Dit schakelt het paneel volledig uit. Dit is een geldige keuze als u het paneel nooit nodig heeft. Als de pagina laadt maar uw token weigert, raadpleeg dan de $$ escaping trap in de onderstaande foutenlijst. Token vergeten? Er is geen hersteloptie; wijzig .env of het Compose file, stel een nieuwe token in, en docker compose up -d.

Stap 6 — verbind de Bitwarden clients

Elke officiële client kan naar een zelfgehoste server verwijzen. Installeer de Bitwarden desktop, mobile of browser client via de reguliere stores — u heeft geen speciale Vaultwarden build nodig.

Open voordat u inlogt het instellingen-icoon (het tandwiel) op het inlogscherm (gelabeld Self-hosted of Region → Self-hosted). Stel de Server URL in op https://vault.example.com en sla de instellingen op. Log vervolgens in met het e-mailadres en het master password dat u heeft geregistreerd. De client maakt direct verbinding en biedt aan om gegevens in te vullen en op te slaan.

Als een client This is not a recognized Bitwarden server. You may need to check with your provider or update your server. aangeeft, is de URL onjuist, wordt http gebruikt, of is het certificaat niet vertrouwd. Controleer eerst of https://vault.example.com correct wordt geladen in een browser. Trage updates op andere apparaten worden veroorzaakt door WebSocket push, dit wordt hieronder behandeld.

Stap 7 — een Fail2ban jail voor het login endpoint

Vaultwarden logt elke mislukte login in het bestand dat is ingesteld via LOG_FILE — precies wat nodig is voor brute-force bescherming. Als u Fail2ban nog niet gebruikt, vindt u de installatie en basisprincipes in de Fail2ban SSH hardening guide; hier voegen we één jail toe voor de vault.

Zoek eerst waar de named volume op de host staat, zodat Fail2ban het logbestand kan lezen:

docker volume inspect vaultwarden_vw-data --format '{{ .Mountpoint }}'

Dit geeft iets weer als /var/lib/docker/volumes/vaultwarden_vw-data/_data; het logbestand staat als vaultwarden.log daarin. Maak de filter aan:

# /etc/fail2ban/filter.d/vaultwarden.conf
[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =

En de jail:

# /etc/fail2ban/jail.d/vaultwarden.local
[vaultwarden]
enabled   = true
filter    = vaultwarden
logpath   = /var/lib/docker/volumes/vaultwarden_vw-data/_data/vaultwarden.log
banaction = iptables-allports
chain     = DOCKER-USER
maxretry  = 5
findtime  = 600
bantime   = 3600

Herlaad met sudo systemctl restart fail2ban en bevestig met sudo fail2ban-client status vaultwarden.

Drie Docker-details bepalen of dit bescherming biedt. Ten eerste: als het logbestand bij elke mislukte poging IP: 127.0.0.1 of het adres van uw proxy toont, wordt de proxy verbannen door Vaultwarden. Stel IP_HEADER in op de header die uw proxy daadwerkelijk verzendt (X-Forwarded-For voor Traefik, X-Real-IP voor de nginx-block hierboven, CF-Connecting-IP achter Cloudflare). Ten tweede: de juiste iptables chain hangt af van uw proxy. Als Traefik draait als een container met gepubliceerde poorten, gaat het verkeer via het FORWARD pad van Docker. De ban moet dan in DOCKER-USER staan zoals hierboven beschreven. Als u echter voor de host-nginx optie uit Stap 1 heeft gekozen, eindigen verbindingen bij nginx op de INPUT chain van de host. Een DOCKER-USER ban ziet deze verbindingen nooit. Verwijder in dat geval de chain = DOCKER-USER regel, zodat Fail2ban de standaard INPUT chain gebruikt. Ten derde: gebruik banaction = iptables-allports in plaats van de standaard poort-gebaseerde instelling. Deze jail definieert geen poort, en een ban op alle poorten in DOCKER-USER blokkeert de overtreder effectief voor elke gepubliceerde service op de machine.

Stap 8 — maak een back-up van de vault, en voer vervolgens de restore uit

Het vw-data volume is uw wachtwoordbeheerder. Het bevat db.sqlite3 (elke vermelding), de attachments/ en sends/ mappen, de rsa_key.* bestanden die loginsessies ondertekenen, en config.json van het admin panel. Een back-up waarbij een van deze onderdelen ontbreekt, is onbruikbaar wanneer u deze nodig heeft.

Het kopiëren van db.sqlite3 terwijl Vaultwarden schrijft, kan resulteren in een beschadigd bestand. Maak daarom een cold snapshot — de downtime bedraagt slechts enkele seconden:

#!/usr/bin/env bash
set -euo pipefail
STAMP=$(date +%F)
DEST=/root/vw-backups
VOL=$(docker volume inspect vaultwarden_vw-data --format '{{ .Mountpoint }}')
mkdir -p "$DEST"
docker compose -f /opt/vaultwarden/docker-compose.yml stop vaultwarden
tar czf "$DEST/vw-$STAMP.tgz" -C "$VOL" .
docker compose -f /opt/vaultwarden/docker-compose.yml start vaultwarden

Voer dit elke nacht uit via cron en kopieer de .tgz van de machine af — een back-up die alleen op de te beschermen server staat, is geen echte back-up. De beste methode is een nachtelijke restic back-up naar een andere server of object storage, waarbij het archief wordt versleuteld en herhaalde snapshots worden gededupliceerd. De knop Backup Database in het admin panel is een handige methode voor een snapshot van alleen het SQLite-bestand, maar bij deze methode ontbreken bijlagen en keys.

Voer nu de handeling uit die een echte back-up onderscheidt van een onzekere back-up — voer een restore uit om te bewijzen dat het werkt:

mkdir -p /tmp/vw-restore
tar xzf /root/vw-backups/vw-2026-07-15.tgz -C /tmp/vw-restore
docker run --rm -p 127.0.0.1:8888:80 -v /tmp/vw-restore:/data vaultwarden/server

Maak vanaf uw laptop een tunnel naar de server met ssh -L 8888:127.0.0.1:8888 you@your-vps en open http://localhost:8888. Omdat localhost een beveiligde context is, is crypto.subtle beschikbaar en wordt de vault via plain http ontsleuteld — de enige toegestane plek. Log in met uw master password en controleer of uw vermeldingen aanwezig zijn: als dit het geval is, zijn uw database, RSA keys en master password correct verwerkt. U kunt dan binnen enkele minuten een nieuwe VPS opbouwen. Stop de container met Ctrl-C en verwijder /tmp/vw-restore.

Foutmodi, met de strings die u zult zien

Cannot read properties of undefined (reading 'importKey') in de browser console. De vault is geladen via http, waardoor crypto.subtle undefined is; bereik deze alleen via https:// en voeg de HTTP-naar-HTTPS redirect toe op de proxy.

This is not a recognized Bitwarden server... in een client. De Server URL is http, bevat een typefout, of het certificaat is onbetrouwbaar; controleer of https://vault.example.com een geldig hangslot toont en voer de URL opnieuw in in de self-hosted instellingen van de client.

/admin weigert het juiste wachtwoord. De Argon2 hash is de escaping verloren — elke $ moet $$ zijn in Compose — of u heeft de hash ingevoerd in plaats van de plaintext die deze vertegenwoordigt.

Trage synchronisatie tussen apparaten; console toont WebSocket connection to 'wss://vault.example.com/notifications/hub' failed. De proxy stuurt de Upgrade/Connection headers niet door; Traefik doet dit automatisch, nginx heeft de twee upgrade lines uit Stap 1 nodig. De vault blijft werken, maar synchroniseert pas bij het openen. De oude dedicated port 3012 is verwijderd sinds v1.31.0, dus een aparte WebSocket route is niet nodig.

Fail2ban meldt een ban maar de aanvaller blijft verbinding maken. Het blokkeert 127.0.0.1 omdat IP_HEADER onjuist is, of de ban staat in de verkeerde iptables chain — stel chain = DOCKER-USER en banaction = iptables-allports in.

Upgrades

Haal de nieuwe image op en maak deze opnieuw aan; de benoemde volume en al uw data blijven behouden:

docker compose pull
docker compose up -d

Vaultwarden brengt regelmatig nieuwe versies uit. Houd de release notes van het project in de gaten in plaats van een specifieke patchversie vast te leggen, omdat sommige releases informatie over migraties bevatten. Maak een nieuwe backup voordat u een grote update uitvoert; u kunt terugdraaien door de tarball te herstellen in een nieuwe volume.

FAQ

Is Vaultwarden hetzelfde als Bitwarden?

Het is een compatibele, onafhankelijke server en niet de officiële versie. Vaultwarden implementeert de Bitwarden server API opnieuw in Rust. Hierdoor werken de officiële desktop-, mobiele-, browser- en CLI-clients ermee, maar met een fractie van de benodigde resources. Het vault-formaat is identiek, dus u kunt in beide richtingen migreren via export en import.

Heb ik echt HTTPS nodig, of kan ik het via http gebruiken op mijn LAN?

U heeft HTTPS nodig voor alles behalve een localhost test. De Bitwarden web vault en extensies gebruiken de Web Crypto API van de browser. Deze is alleen beschikbaar in een beveiligde context. Via gewone http geeft de client Cannot read properties of undefined en vindt er geen login plaats. Het enige werkende http-adres is http://localhost. Daarom gebruikt de restore-test in Stap 8 een SSH-tunnel.

Hoe voorkom ik dat vreemden zich registreren op mijn server?

Stel SIGNUPS_ALLOWED: "false" in in het Compose-bestand en voer docker compose up -d uit direct nadat u uw eigen account heeft aangemaakt. Voeg vanaf dat moment nieuwe gebruikers toe via de Invite User knop in /admin. Hiervoor moet SMTP zijn geconfigureerd, zodat gebruikers de uitnodigingslink ontvangen. Controleer periodiek de lijst met beheerders om te bevestigen dat er geen onverwachte accounts zijn aangemaakt.

Hoe maak ik een back-up van mijn Vaultwarden vault?

Stop de container kortstondig en archiveer het volledige vw-data volume — db.sqlite3, attachments/, sends/, config.json en de rsa_key.* bestanden. Kopieer het archief vervolgens naar een andere locatie, bij voorkeur via een nachtelijke cron. Het kopiëren van het actieve SQLite-bestand terwijl de server draait, kan leiden tot een beschadigde snapshot. Maak daarom een offline back-up. Belangrijker nog: test de back-up eenmalig in een tijdelijke container door in te loggen. Zo weet u zeker dat de back-up werkt voordat u er volledig op vertrouwt.

Is het echt veilig om mijn wachtwoorden zelf te hosten?

Ja, mits u de drie zaken uitvoert die in deze handleiding worden behandeld: echte HTTPS, gesloten registraties met een sterke admin token, en getest back-upbeleid. Uw vault wordt aan de client-zijde versleuteld met uw master password. De server ziet uw wachtwoorden daarom nooit in klare tekst; een gestolen db.sqlite3 is zonder dit wachtwoord nutteloos. Het nadeel is dat het patchen en de back-ups nu uw verantwoordelijkheid zijn. Daarom zijn Fail2ban en het restore-ritueel hier niet optioneel.

#vaultwarden#passwords#security#docker#self-hosting