Installare Vaultwarden su VPS con Docker
Guida all'auto-hosting di Vaultwarden su VPS con Docker. Configura HTTPS, admin token e backup sicuri per gestire le tue password in totale privacy.
Cosa stai costruendo
Un password manager di tua completa proprietà: Vaultwarden eseguito in un singolo container leggero dietro un reverse proxy che gestisce la terminazione HTTPS, con le app ufficiali di Bitwarden su smartphone, laptop e browser configurate per connettersi ad esso. Vaultwarden re-implementa l'API del server Bitwarden in Rust e utilizza lo stesso protocollo di bitwarden.com; pertanto, ogni client ufficiale funziona senza modifiche. Vaultwarden occupa circa 100 MB di RAM, a differenza dello stack ufficiale composto da più container.
L'installazione consiste in una dozzina di righe di Compose. I tre elementi fondamentali — e i principali punti critici — sono i seguenti: il TLS deve essere attivo prima di caricare il web vault, le registrazioni pubbliche devono essere disabilitate non appena viene creato il proprio account, e il volume dei dati deve essere sottoposto a backup e test di ripristino, poiché quella singola directory contiene tutte le tue password.
Prerequisiti e potenziali problemi
- Un VPS con Docker Engine e il plugin Compose, su un sistema Ubuntu 24.04 KVM appena installato con privilegi root o sudo. 512 MB di RAM sono sufficienti; 1 GB garantisce prestazioni ottimali. Questo è uno dei servizi più leggeri disponibili — si trova tra i primi della lista dei servizi da self-hostare.
- Un dominio con un record A (e AAAA se si utilizza IPv6) che punti
vault.example.comverso il VPS. Il certificato TLS viene emesso per questo specifico nome, quindi la risoluzione DNS deve essere attiva prima di iniziare. - Porte 80 e 443 aperte su internet e gestite dal reverse proxy — mai direttamente da Vaultwarden. La porta 80 serve esclusivamente per la challenge ACME del certificato e per il redirect da HTTP a HTTPS.
- Il problema principale iniziale: i client di Bitwarden non comunicano con server che non utilizzano HTTPS. Non è possibile effettuare test via HTTP — quel metodo non funziona per una ragione specifica descritta nel prossimo passaggio.
Perché Vaultwarden invece dello stack ufficiale Bitwarden
Stessi client, una frazione del peso. La versione self-hosted ufficiale di Bitwarden viene distribuita come un insieme di container (MSSQL, Nginx, Identity, Api, Admin e altri) e richiede circa 2 GB di RAM. Vaultwarden è un singolo binario che memorizza tutto in un database SQLite di default e consuma pochi decine di megabyte in idle. Per un singolo utente, una famiglia o un piccolo team è la scelta ovvia; poiché implementa fedelmente l'API di Bitwarden, i dati rimangono portabili tra Vaultwarden e bitwarden.com.
Si rinuncia alla maggior parte delle funzionalità enterprise: non è presente il provisioning SCIM (anche se l'OpenID Connect SSO sperimentale è stato introdotto nella versione 1.35.0). L'utente è l'operatore, quindi la gestione di patch, HTTPS e backup è a proprio carico. Questa guida copre questi tre compiti.
Perché HTTPS non è opzionale
Il web vault di Bitwarden e le estensioni per browser derivano le chiavi di cifratura nel browser utilizzando la Web Crypto API (window.crypto.subtle). I browser espongono crypto.subtle solo in un secure context — ovvero HTTPS o il caso specifico di http://localhost. Su semplice http://vault.example.com l'operazione è undefined; pertanto, non appena l'app tenta di derivare una chiave, l'operazione fallisce e la console mostra:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'importKey')La pagina si blocca o mostra un errore generico di crittografia, e l'accesso non avviene. I client desktop, mobile e browser eseguono un controllo proprietario sull'URL self-hosted; se l'endpoint è http (o non raggiungibile), il client rifiuta la connessione con l'errore:
This is not a recognized Bitwarden server. You may need to check with your provider or update your server.Entrambi i casi derivano dalla stessa causa: assenza di un protocollo HTTPS valido. È necessario configurare TLS prima di tutto e non aprire mai il vault tramite http, nemmeno per una rapida consultazione.
Step 1 — DNS e reverse proxy (TLS first)
Punta il record verso il tuo VPS e conferma che risolva all'indirizzo corretto:
dig +short vault.example.comL'output deve mostrare l'IP del tuo VPS. Se l'output è vuoto o errato, correggi il DNS e attendi la scadenza del TTL; l'emissione del certificato fallisce se il nome non risolve.
Per il front end HTTPS questa guida utilizza Traefik, che emette e rinnova i certificati Let's Encrypt automaticamente e si integra direttamente con Compose. Se non lo stai già utilizzando, segui prima la configurazione del reverse proxy Traefik e del TLS automatico; questo crea una rete Docker esterna (proxy sotto) e un resolver ACME (letsencrypt) a cui si connette il servizio Vaultwarden. Un semplice nginx con certificato manuale funziona in modo identico lato Vaultwarden.
Preferisci nginx e Certbot invece di Traefik? Posiziona Vaultwarden su 127.0.0.1:8080 (aggiungi ports: ["127.0.0.1:8080:80"] al servizio e rimuovi le labels di Traefik), quindi emetti un certificato e instrada il traffico tramite proxy verso di esso. La parte relativa al certificato è trattata in emissione di certificati Let's Encrypt con Certbot e nginx. L'elemento extra fondamentale è l'upgrade WebSocket sul percorso delle notifiche:
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";
}
}Nota la riga X-Real-IP: è ciò che permette a Fail2ban di identificare l'attaccante reale invece di 127.0.0.1. Tutto il resto di questa guida rimane identico, indipendentemente dal fatto che sia utilizzato Traefik o nginx come proxy.
Step 2 — il file Compose
Crea prima la directory del progetto. Questa guida utilizza /opt/vaultwarden, che rende prevedibile il nome del progetto Compose — e quindi il volume dati, vaultwarden_vw-data —; i passaggi relativi a Fail2ban e al backup descritti sotto dipendono da quel nome esatto.
sudo mkdir -p /opt/vaultwarden
cd /opt/vaultwardenCrea una .env per l'admin secret e il file Compose in quella directory.
# .env
ADMIN_TOKEN=paste-a-strong-token-hereGenera il token con openssl rand -base64 48 e incollalo. (Il prossimo passaggio riguarda una forma hash più sicura; per iniziare una stringa casuale lunga è sufficiente.)
# 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: trueDue elementi di questo file definiscono l'intero design. Non è presente alcun mapping di ports:, quindi Vaultwarden è raggiungibile solo tramite Traefik e il suo TLS — pubblicare la sua porta sull'host è il motivo per cui gli utenti espongono accidentalmente il vault via http. Inoltre, DOMAIN deve essere l'URL HTTPS pubblico completo: è integrato nei link degli allegati, nel WebAuthn 2FA e nell'endpoint delle notifiche; un valore errato o in http ne blocca il funzionamento anche se il sito si carica. Il tag latest è un'eccezione deliberata alla consueta regola di non-latest — Vaultwarden rilascia le versioni stabili come un'unica immagine rolling, con :testing come canale separato per le pre-release — quindi aggiorna intenzionalmente e leggi le note di rilascio prima di eseguire il pull.
Avvia il servizio e monitora il log:
docker compose up -d
docker compose logs -f vaultwardenUn avvio corretto termina con una riga simile a Rocket has launched from http://0.0.0.0:80. Lascia a Traefik alcuni secondi per recuperare il certificato, quindi carica https://vault.example.com — dovresti visualizzare il web vault di Bitwarden con un lucchetto valido e senza avvisi relativi al certificato.
Step 3 — un ADMIN_TOKEN robusto e la trappola $$
ADMIN_TOKEN protegge /admin, il pannello che può leggere ogni utente e ogni impostazione della vostra instance; trattatelo come una password di root. Sono possibili due formati.
Il formato semplice è la stringa casuale generata precedentemente con openssl rand -base64 48. Poiché base64 non contiene mai un $, viene inserito direttamente in .env senza necessità di escaping.
Il formato protetto è un hash Argon2 PHC, così il token in chiaro non viene mai salvato su disco. Generatene uno utilizzando la stessa immagine:
docker run --rm -it vaultwarden/server /vaultwarden hash --preset owaspIl comando richiede l'inserimento due volte e stampa una stringa che inizia con $argon2id$v=19$.... Ecco la trappola che causa un'ora di lavoro extra: Docker Compose interpreta $ come interpolazione di variabili; pertanto, è necessario raddoppiare ogni $ in $$ quando si incolla l'hash nel file Compose. Inseritelo direttamente sotto environment:, non tramite .env, e non utilizzate le virgolette:
environment:
ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$c29tZXNhbHQ$$RdescudvJCsgt3ub+b+dWRWJTmaaJObGSe lasciate i singoli segni $, Compose emette un avviso The "argon2id" variable is not set e svuota il token; di conseguenza, /admin rifiuterà la password corretta. Eseguite docker compose up -d e salvate il testo in chiaro digitato nel prompt nel vostro password store personale.
Step 4 — registra il tuo account, poi chiudi l'accesso
Con SIGNUPS_ALLOWED: "true", apri https://vault.example.com, clicca su Create account e registrati usando la tua email e una password master sicura. La password master non è mai recuperabile — non esiste un reset — quindi conservala in un luogo sicuro prima di procedere.
Ora chiudi l'accesso. Modifica il file Compose per disabilitare le registrazioni:
SIGNUPS_ALLOWED: "false"Applica nuovamente la modifica con docker compose up -d. Questa operazione non è critica e può essere posticipata. Se lasci l'accesso aperto, chiunque trovi l'URL — inclusi i crawler — può creare un account sul tuo server. Non possono leggere il tuo vault, ma consumano risorse e trasformano la tua istanza privata in un servizio pubblico. Il segnale che l'accesso è ancora aperto: /admin elenca account che non hai mai creato.
Per aggiungere familiari o colleghi in seguito senza riaprire le registrazioni pubbliche, usa il pulsante Invite User in /admin; questa funzione richiede la configurazione di SMTP affinché il destinatario riceva il link di invito.
Step 5 — accesso a /admin
Naviga verso https://vault.example.com/admin ed inserisci il token admin in chiaro (la stringa casuale o la password che hai hashato — non l'hash stesso). All'interno puoi elencare gli utenti, configurare le impostazioni, inviare un'email di prova e creare uno snapshot del database.
Se la pagina restituisce 404 Not Found, ADMIN_TOKEN è vuoto o non impostato, il che disabilita completamente il pannello — è una scelta valida se non è mai necessario utilizzarlo. Se la pagina si carica ma rifiuta il token, consulta il problema dell'escaping di $$ nell'elenco degli errori sotto. Hai dimenticato il token? Non esiste una procedura di recupero; modifica .env o il file Compose, imposta un nuovo token e docker compose up -d.
Step 6 — connettere i client Bitwarden
Ogni client ufficiale può puntare a un server self-hosted. Installa il client Bitwarden per desktop, mobile o browser dagli store ufficiali; non è necessaria una build specifica di Vaultwarden.
Prima di effettuare il login, clicca sull'icona delle impostazioni nella schermata di login (etichettata come Self-hosted o Region → Self-hosted), imposta il Server URL su https://vault.example.com e salva. Effettua quindi il login con l'email e la master password registrate; il client dovrebbe connettersi immediatamente e offrire la compilazione e il salvataggio delle credenziali.
Se un client mostra This is not a recognized Bitwarden server. You may need to check with your provider or update your server., l'URL è errato, utilizza http oppure il certificato non è affidabile; verifica prima che https://vault.example.com sia caricabile correttamente in un browser. Gli aggiornamenti lenti su altri dispositivi sono causati dal push WebSocket, descritto di seguito.
Step 7 — una jail di Fail2ban per l'endpoint di login
Vaultwarden registra ogni tentativo di login fallito nel file impostato tramite LOG_FILE; è esattamente ciò di cui ha bisogno un sistema di protezione contro il brute-force. Se Fail2ban non è già installato, la procedura di installazione e i concetti base sono disponibili nella guida all'hardening di Fail2ban per SSH; qui aggiungiamo una jail specifica per il vault.
Per prima cosa, individua la posizione del volume nominato sull'host, in modo che Fail2ban possa leggere il log:
docker volume inspect vaultwarden_vw-data --format '{{ .Mountpoint }}'Il comando restituisce un output simile a /var/lib/docker/volumes/vaultwarden_vw-data/_data; il log si trova in vaultwarden.log all'interno di tale percorso. Crea il filtro:
# /etc/fail2ban/filter.d/vaultwarden.conf
[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =E la 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 = 3600Ricarica la configurazione con sudo systemctl restart fail2ban e conferma con sudo fail2ban-client status vaultwarden.
Tre dettagli di Docker determinano l'efficacia della protezione. Primo, se il log mostra IP: 127.0.0.1 o l'indirizzo del proxy per ogni tentativo fallito, Vaultwarden bannerà il proxy; imposta IP_HEADER sull'header inviato effettivamente dal proxy (X-Forwarded-For per Traefik, X-Real-IP per il blocco nginx sopra riportato, CF-Connecting-IP se si è dietro Cloudflare). Secondo, la catena iptables corretta dipende dal proxy: con Traefik eseguito come container con porte pubblicate, il traffico attraversa il percorso Docker FORWARD, quindi il ban deve essere applicato a DOCKER-USER come indicato sopra; se invece è stata scelta l'opzione host-nginx dello Step 1, le connessioni terminano su nginx nella catena INPUT dell'host e un ban su DOCKER-USER non le intercetterà; in tal caso, elimina la riga chain = DOCKER-USER affinché Fail2ban utilizzi la catena predefinita INPUT. Terzo, usa banaction = iptables-allports invece del default basato sulla porta: questa jail non definisce alcuna porta, e un ban su tutte le porte in DOCKER-USER blocca correttamente l'utente malintenzionato da ogni servizio pubblicato sul server.
Step 8 — eseguire il backup del vault, quindi procedere al ripristino
Il volume vw-data è il tuo password manager. Contiene db.sqlite3 (ogni voce), le directory attachments/ e sends/, i file rsa_key.* per la firma delle sessioni di login e config.json dal pannello admin. Un backup che omette uno di questi elementi fallirà quando ne avrai bisogno.
Copiare db.sqlite3 mentre Vaultwarden sta scrivendo può generare un file corrotto o incompleto; è necessario eseguire uno snapshot a freddo — il downtime è di pochi secondi:
#!/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 vaultwardenEsegui il comando tramite cron ogni notte e copia .tgz fuori dal server — un backup conservato solo sul server che stai proteggendo non è un backup. Il metodo più efficiente è un backup restic notturno su un altro server o object storage, che cifra l'archivio e gestisce la deduplicazione degli snapshot ripetuti. Il pulsante Backup Database nel pannello admin crea un rapido snapshot del solo file SQLite, ma esclude allegati e chiavi.
Ora la procedura che distingue un backup reale da uno incerto — esegui il ripristino una volta per verificarne il funzionamento:
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/serverDal tuo laptop, crea un tunnel verso il server tramite ssh -L 8888:127.0.0.1:8888 you@your-vps e apri http://localhost:8888. Poiché localhost è un contesto sicuro, crypto.subtle è disponibile e il vault viene decifrato via http semplice — l'unico caso in cui è consentito. Accedi con la tua master password e verifica la presenza delle voci: se sono presenti, il database, le chiavi RSA e la master password sono stati trasferiti correttamente e puoi ricostruire il sistema su un nuovo VPS in pochi minuti. Ferma il container con Ctrl-C e cancella /tmp/vw-restore.
Modalità di errore e relativi messaggi
Cannot read properties of undefined (reading 'importKey') nella console del browser. Il vault è stato caricato tramite http, quindi crypto.subtle è undefined; accedervi solo tramite https:// e aggiungere il redirect HTTP-to-HTTPS sul proxy.
This is not a recognized Bitwarden server... su un client. L'URL del Server è http, errato, oppure il certificato non è affidabile; verificare che https://vault.example.com mostri un lucchetto valido, quindi reinserirlo nelle impostazioni self-hosted del client.
/admin rifiuta la password corretta. L'hash Argon2 ha perso l'escaping — ogni $ deve essere $$ in Compose — oppure è stato inserito l'hash invece del testo in chiaro che rappresenta.
Sincronizzazione cross-device lenta; la console mostra WebSocket connection to 'wss://vault.example.com/notifications/hub' failed. Il proxy non sta inoltrando gli header Upgrade/Connection; Traefik lo esegue automaticamente, nginx richiede le due righe upgrade dello Step 1. Il vault funziona comunque, ma la sincronizzazione avviene solo all'apertura. La vecchia porta dedicata 3012 è stata rimossa dalla v1.31.0, quindi non è necessaria una rotta WebSocket separata.
Fail2ban segnala un ban ma l'attaccante continua a connettersi. Sta bannando 127.0.0.1 perché IP_HEADER è errato, oppure il ban si trova nella catena iptables sbagliata — impostare chain = DOCKER-USER e banaction = iptables-allports.
Upgrades
Scarica la nuova immagine e ricrea il container; il volume nominato e tutti i dati persistono:
docker compose pull
docker compose up -dVaultwarden rilascia aggiornamenti frequenti. Monitora le release notes del progetto invece di bloccare una specifica patch version, poiché alcuni rilasci contengono note sulla migrazione. Effettua un backup completo prima di ogni aggiornamento importante; è possibile effettuare il rollback ripristinando il file tarball in un nuovo volume.
FAQ
Vaultwarden è uguale a Bitwarden?
È un server indipendente e compatibile, non è quello ufficiale. Vaultwarden re-implementa l'API del server Bitwarden in Rust. Per questo motivo i client ufficiali per desktop, mobile, browser e CLI funzionano correttamente con esso, consumando una frazione delle risorse dello stack ufficiale. Il formato del vault è identico, quindi è possibile migrare in entrambe le direzioni tramite export e import.
È necessario HTTPS o posso usare http nella mia LAN?
HTTPS è necessario per qualsiasi operazione che non sia un test localhost. Il web vault e le estensioni di Bitwarden utilizzano la Web Crypto API del browser. Questa API è disponibile solo in un contesto sicuro; pertanto, tramite http semplice, il client restituisce l'errore Cannot read properties of undefined e non effettua il login. L'unico indirizzo http funzionante è http://localhost, motivo per cui il test di restore nello Step 8 utilizza un tunnel SSH.
Come posso impedire a estranei di registrarsi sul mio server?
Imposta SIGNUPS_ALLOWED: "false" nel file Compose ed esegui docker compose up -d subito dopo aver creato il tuo account. Da quel momento, aggiungi nuovi utenti tramite il pulsante Invite User in /admin. È necessaria la configurazione di SMTP affinché gli utenti ricevano il link di invito. Controlla periodicamente la lista degli utenti admin per verificare che non siano apparsi account non autorizzati.
Come posso eseguire il backup del mio vault Vaultwarden?
Ferma brevemente il container e archivia l'intero volume vw-data — i file db.sqlite3, attachments/, sends/, config.json e rsa_key.* — quindi copia l'archivio fuori dal server, idealmente tramite un cron notturno. Copiare il file SQLite mentre il server è in esecuzione rischia di creare uno snapshot corrotto; esegui il backup a server spento. Fondamentalmente, esegui un restore una volta in un container di prova e effettua il login, così da confermare la validità del backup prima di renderlo indispensabile.
È sicuro gestire le proprie password in self-hosting?
Sì, se vengono eseguite le tre operazioni descritte in questa guida: HTTPS reale, registrazioni disabilitate con un forte token admin e backup testati. Il vault è criptato lato client con la master password; pertanto, il server non vede mai le password in chiaro. Un db.sqlite3 rubato è inutile senza di essa. Il compromesso è che la gestione delle patch e dei backup è ora a tuo carico; per questo Fail2ban e la procedura di restore non sono opzionali.