SSD Nodes Learn 🎉 VPS da $5.50/mese
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-13

Vaultwarden è sicuro? Guida all’hardening

Vaultwarden cifra ogni elemento sul client e non conserva dati in chiaro. Proteggi soprattutto token admin, porte esposte e file di backup.

Vaultwarden è sicuro? La risposta breve

Vaultwarden è sicuro nel punto più importante: ogni elemento del vault viene cifrato sul dispositivo prima di raggiungere il server. Il server memorizza dati binari che non può leggere. Anche chi copia l’intero database ha comunque bisogno della password principale per ricavarne informazioni utili.

Questa risposta dipende da molti fattori, e i problemi si verificano nelle parti che configuri. Un pannello di amministrazione protetto da un token facilmente indovinabile. Una porta del container pubblicata sull’intera Internet. Un config.json in testo non cifrato. Un archivio tar di backup lasciato nella home directory sullo stesso server. Nessuno di questi casi è un problema di crittografia. Sono tutti motivi per cui i vault self-hosted vengono svuotati.

Tutto ciò che segue presuppone un’installazione funzionante. Se non ne hai ancora una, configúrala prima seguendo la guida all’installazione di Vaultwarden su un VPS, quindi torna qui e segui questo elenco in ordine.

Che cosa memorizza realmente il server

Vaultwarden implementa il modello dati di Bitwarden. Il nome, il nome utente, la password, le note e gli URI di un elemento del vault vengono cifrati nel client con una chiave derivata dalla password principale, prima dell'invio di qualsiasi richiesta. Anche il contenuto degli allegati viene cifrato nello stesso modo. Il server riceve dati non interpretabili associati a un UUID (identificatore univoco universale).

Alcuni dati non sono cifrati. È importante sapere esattamente quali:

  • L'indirizzo email dell'account, in chiaro.
  • Le impostazioni e il salt della KDF (funzione di derivazione della chiave), perché il client deve usarli per ricostruire la chiave al login successivo.
  • Un hash lato server dell'hash della password principale inviato dal client, usato per autenticare il login.
  • Metadati: appartenenza alle organizzazioni, nomi dei dispositivi, orari degli ultimi login.
  • Il secret del metodo a due fattori che protegge il login a Vaultwarden. Si trova non cifrato nella tabella twofactor, perché il server deve calcolare il codice atteso e confrontarlo con quello fornito. Non è lo stesso secret TOTP (password monouso basata sul tempo) che memorizzi all'interno di un elemento del vault e che viene cifrato come qualsiasi altro campo.

La directory dei dati è di dimensioni ridotte. In un'installazione Docker corrisponde al percorso montato in /data.

sudo ls -l /vw-data/

db.sqlite3 contiene quasi tutto lo stato dell'applicazione. attachments/ contiene i file caricati, uno per UUID, ed è l'unica classe importante di dati che non si trova nelle tabelle del database. sends/ contiene gli allegati di Send ed è destinata ai dati temporanei. icon_cache/ contiene dati eliminabili. rsa_key.pem e i file associati firmano i JWT (JSON Web Token) degli utenti autenticati. Una copia di questa chiave privata può quindi essere usata per falsificare una sessione di login al vault. config.json viene creata solo dopo l'abilitazione della pagina di amministrazione. Il progetto lo dichiara esplicitamente: contiene in chiaro il token di amministrazione e le credenziali SMTP.

Il modello pratico delle minacce riguarda quindi l'accesso al filesystem, non la cifratura del traffico di rete. L'accesso in lettura a quella directory espone l'indirizzo email di ogni utente, i secret 2FA dei login, una chiave con cui falsificare le sessioni e una copia offline di ogni vault, che può essere attaccata senza limiti di tempo. Ogni passaggio seguente serve a impedire l'accesso non autorizzato a quella directory.

Correggere prima il token amministratore

/admin è un pannello di controllo completo: elenco degli utenti, inviti, eliminazione e tutte le impostazioni di runtime. È protetto soltanto da un secret condiviso. Non è previsto alcun nome utente né l'autenticazione a due fattori per singolo utente.

Le guide meno recenti indicano di generare ADMIN_TOKEN con openssl rand -base64 48. Questa procedura funziona e scrive il secret in chiaro in config.json e nel file compose. Vaultwarden accetta anche una stringa PHC (password hashing competition) Argon2, quindi il valore memorizzato è invece un hash. Generatene uno usando un container in esecuzione:

docker exec -it vaultwarden /vaultwarden hash

Oppure senza intervenire sul container in esecuzione:

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

Il comando richiede la password due volte, quindi stampa una riga che inizia con $argon2id$. In un'installazione bare metal, eseguite ./vaultwarden hash. Se preferite usare direttamente la CLI argon2, il progetto documenta i parametri minimi OWASP:

echo -n 'MySecretPassword' | argon2 "$(openssl rand -base64 32)" -e -id -k 19456 -t 2 -p 1

Ora analizziamo l'errore che può far perdere un'ora. Una stringa PHC contiene molti caratteri $ e Docker Compose interpreta $ come interpolazione di variabili. Se la incollate senza eseguire l'escape in un blocco environment:, il valore ricevuto dal container viene alterato e /admin rifiuta un token che sapete essere corretto. Esistono due forme sicure. In docker-compose.yml, raddoppiate ogni $:

environment:
  ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$UUZxK1FZMkZoRHFQRlVrTXZvS0E3bHpNQW55c2dBN2NORzdsa0Nxd1JhND0$$cUoId+JBUsJutlG4rfDZayExfjq4TCt48aBc9qsc3UI

In un file .env non è necessario eseguire l'escape, ma usate gli apici singoli:

ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'

Quindi limitate la frequenza di accesso al pannello e riducete la durata della sessione:

ADMIN_RATELIMIT_SECONDS=300
ADMIN_RATELIMIT_MAX_BURST=3
ADMIN_SESSION_LIFETIME=20

Dopo tre tentativi non riusciti entro cinque minuti, il pannello smette di rispondere a quel client. La sessione amministrativa scade dopo 20 minuti di inattività.

La soluzione migliore è disattivare la pagina. La maggior parte delle installazioni ne ha bisogno una sola volta, per configurare SMTP e invitare i primi utenti. In seguito non serve più. Per disattivarla, non impostate né ADMIN_TOKENDISABLE_ADMIN_TOKEN, rimuovete qualsiasi chiave "admin_token" da config.json, quindi ricreate il container. Rimuovere la chiave dal file è importante perché la pagina di amministrazione scrive lì le impostazioni e il valore presente in config.json ha precedenza sull'ambiente. Rimuovere soltanto la variabile lascia la pagina accessibile.

Chiudi le registrazioni prima che qualcuno trovi il dominio

SIGNUPS_ALLOWED=false
SIGNUPS_VERIFY=true
SHOW_PASSWORD_HINT=false

SIGNUPS_ALLOWED è impostato per impostazione predefinita su true. Lascia questa impostazione e chiunque raggiunga il dominio potrà creare un account; i relativi dati verranno archiviati nello stesso db.sqlite3 dei tuoi. Impostalo su false e aggiungi gli utenti tramite inviti dalla pagina di amministrazione; questa funzione richiede un SMTP operativo. Per impostazione predefinita, INVITATIONS_ALLOWED è anch'esso true e consente ai proprietari delle organizzazioni di invitare altre persone. Questa configurazione è adeguata quando gli utenti sono affidabili e, in un'istanza utilizzata da una sola persona, dovrebbe essere impostata su false. Se devono essere consentite registrazioni soltanto da determinati domini, SIGNUPS_DOMAINS_WHITELIST=example.com è più restrittivo della registrazione aperta, ma offre garanzie molto inferiori rispetto agli inviti.

SHOW_PASSWORD_HINT è impostato per impostazione predefinita su false e dovrebbe rimanere così. Quando è attivo, l'inserimento di un indirizzo email valido nel modulo di accesso restituisce il suggerimento della master password dell'account corrispondente. Questo espone il suggerimento e conferma che l'indirizzo esiste.

Se l'istanza ha consentito registrazioni aperte per un periodo qualsiasi, apri la pagina di amministrazione e controlla l'elenco degli utenti prima di presumere che l'account sia l'unico presente.

La porta che non volevi pubblicare

L'immagine Docker è in ascolto sulla porta 80 all'interno del container. Un'installazione bare metal usa ROCKET_PORT=8000 per impostazione predefinita. Il comando di esecuzione documentato la pubblica in questo modo:

--publish 127.0.0.1:8000:80

Il prefisso 127.0.0.1: è l'elemento fondamentale. Se scrivi invece -p 8000:80, Docker esegue il binding di 0.0.0.0 e lo fa scrivendo regole DNAT (destination network address translation) nella tabella nat. Queste regole vengono valutate prima delle catene filter gestite da ufw. Di conseguenza, ufw status segnala la porta come negata, mentre la porta continua a rispondere da Internet. Per una descrizione completa del meccanismo, consulta la guida alle porte Docker che bypassano ufw.

Verifica cosa è realmente in ascolto:

sudo ss -tlnp | grep 8000

Un risultato corretto contiene una sola riga associata a 127.0.0.1:8000. Una riga associata a 0.0.0.0:8000 indica che il vault è esposto direttamente. Correggi il mapping, quindi ricrea il container, perché il binding di una porta viene fissato quando il container viene creato e docker compose restart non lo modifica:

docker compose up -d --force-recreate

Nelle vecchie guide compare ancora un'altra porta: 3012, la porta WebSocket separata. Il supporto per questa porta è stato rimosso in Vaultwarden 1.31.0, perché il traffico delle notifiche è stato spostato sulla porta HTTP principale. WEBSOCKET_ENABLED e WEBSOCKET_PORT vengono ignorati dalla versione 1.29.0. L'impostazione attuale è ENABLE_WEBSOCKET, il cui valore predefinito è true. Se il firewall o il file compose apre ancora la porta 3012, chiudila.

Termina TLS su un reverse proxy, non in Rocket

Vaultwarden può gestire direttamente TLS (transport layer security) tramite Rocket, il suo framework web, ma il progetto sconsiglia questa configurazione in produzione. Il supporto TLS integrato di Rocket non implementa correttamente SNI (server name indication). Per questo le indicazioni di hardening raccomandano di raggiungere l'istanza tramite hostname e mai tramite un semplice indirizzo IP. Gli intervalli IP pubblici vengono sottoposti continuamente a scansione. Un vault che risponde su un indirizzo IP viene individuato.

Le parti di un server block di nginx che contano sono:

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;
}

Per impostazione predefinita, nginx client_max_body_size a 1 MB. Senza quella riga, il caricamento di un allegato fallisce con 413 Request Entity Too Large nel log degli errori di nginx, mentre Vaultwarden non registra alcun messaggio. Gli header Upgrade e Connection trasferiscono l'handshake WebSocket a /notifications/hub. Se li ometti, il vault continua a funzionare, ma le modifiche smettono di comparire sugli altri dispositivi finché non ricarichi manualmente la pagina.

La configurazione di Caddy è più breve e il certificato viene ottenuto automaticamente:

vw.example.com {
  reverse_proxy 127.0.0.1:8000 {
    header_up X-Real-IP {remote_host}
  }
}

Poi comunica queste informazioni a Vaultwarden:

DOMAIN=https://vw.example.com
IP_HEADER=X-Real-IP

IP_HEADER è già impostato per impostazione predefinita su X-Real-IP. Devi quindi verificare che il proxy imposti realmente quell'header. In caso contrario, ogni riga di log e ogni limite di frequenza per i login vedono 127.0.0.1, cioè il proxy stesso. Di conseguenza, i tentativi falliti di un singolo attaccante vengono conteggiati per tutti gli utenti dell'istanza. Imposta anche DOMAIN sull'URL https reale. Vaultwarden lo usa per generare i link di invito e di reimpostazione della password. Le chiavi di sicurezza WebAuthn sono associate a quell'origine.

Un dettaglio spesso trascurato è che la connessione WebSocket trasmette il token di sessione nella stringa di query, come /notifications/hub?access_token=[JWT]. Il valore finisce in chiaro nel log degli accessi del proxy. Oscura il parametro access_token nel formato del log oppure assicurati che questi log non vengano inviati a sistemi che non controlli.

Blocca i tentativi di forza bruta sull'endpoint di accesso

I limiti di frequenza sono attivi per impostazione predefinita (LOGIN_RATELIMIT_SECONDS=60, LOGIN_RATELIMIT_MAX_BURST=10). Rallentano un attaccante, ma non lo bloccano. Questo compito spetta a fail2ban. Tuttavia, prima Vaultwarden deve scrivere un file di log, cosa che non fa automaticamente:

LOG_FILE=/data/vaultwarden.log
LOG_LEVEL=info
EXTENDED_LOGGING=true

Un accesso non riuscito produce quindi esattamente una riga. Questa è la stringa che il filtro deve trovare:

[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.

Scrivi il filtro in /etc/fail2ban/filter.d/vaultwarden.local:

[INCLUDES]
before = common.conf

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

E il jail in /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 = 14400

Se hai mantenuto la pagina di amministrazione, aggiungi un secondo jail con failregex impostato su ^.*Invalid admin token\. IP: <ADDR>.*$. I tentativi di accesso amministrativo non riusciti vengono registrati con un messaggio diverso e il filtro per gli accessi non li rileverà. Poi verifica la configurazione:

sudo systemctl restart fail2ban
sudo fail2ban-client status vaultwarden

Un jail funzionante elenca il file di log in File list e riporta Currently failed: 0. Inserisci per tre volte una password errata da una rete diversa. Il contatore aumenta e l'indirizzo compare in Banned IP list. Se il contatore non cambia, la causa più comune è logpath: deve essere il percorso del file sull'host, non il percorso /data/... all'interno del container. La seconda causa più comune è l'assenza di X-Real-IP, che fa puntare ogni ban al proxy stesso. Il resto della configurazione, incluso il jail SSH che dovresti già avere attivo, è disponibile nella guida a fail2ban per Ubuntu 24.04.

La password principale resta la protezione dell'intero sistema

La crittografia lato client significa che la password principale è la chiave crittografica. Una password principale breve, usata su un'istanza il cui database è stato copiato da un attaccante, non è protetta da nulla di quanto descritto in questo articolo, perché l'attaccante può eseguire l'attacco offline su quella copia alla velocità consentita dal proprio hardware. Nessuna impostazione del server può limitare un attacco eseguito sul computer dell'attaccante.

PASSWORD_ITERATIONS=600000 è il numero di iterazioni del KDF inviato ai client quando creano un nuovo account. Gli account esistenti mantengono il valore con cui sono stati creati, quindi aumentarlo non cambia nulla per gli utenti che si sono registrati l'anno scorso. Devono modificarlo autonomamente nelle impostazioni di sicurezza del web vault; questa operazione ricrittografa la loro chiave. È necessario informarli, perché l'interfaccia non lo segnala.

Abilita quindi l'autenticazione a due fattori per ogni account. Non protegge il testo cifrato, perché la chiave del vault deriva soltanto dalla password principale. Impedisce però che una password sottratta sia sufficiente per accedere e sincronizzare una copia. REQUIRE_DEVICE_EMAIL=true aggiunge una conferma tramite email la prima volta che un account accede da un dispositivo non riconosciuto.

I backup sono il punto in cui i vault self-hosted possono presentare problemi

Un tar czf della directory dei dati, lasciato in una directory home sullo stesso VPS, annulla tutti i passaggi precedenti. L'archivio contiene db.sqlite3 con il testo cifrato di ogni utente, rsa_key.pem che consente di falsificare le sessioni di accesso e config.json con il token amministrativo e la password SMTP in testo in chiaro. L'accesso in lettura a quel file equivale all'accesso in lettura al vault.

Sono sufficienti due regole. Spostare l'archivio fuori dal server. Cifrarlo prima di trasferirlo.

Esiste anche un problema di integrità. Copiare db.sqlite3 con cp mentre il servizio è in esecuzione può produrre un file in fase di scrittura che non si apre. Il problema potrebbe essere rilevato solo durante il ripristino. Usare invece lo snapshot nativo di SQLite:

sqlite3 /vw-data/db.sqlite3 ".backup '/tmp/vw-db-backup.sqlite3'"

La procedura di ripristino, che spesso non viene mai verificata, è descritta nella guida al backup e al ripristino di Vaultwarden.

Cosa si rinuncia rispetto a Bitwarden in hosting

Valutazione onesta. Il servizio in hosting di Bitwarden è gestito da persone il cui lavoro a tempo pieno consiste nell'amministrarlo, con audit di terze parti pubblicati e qualcuno reperibile alle 3 di notte. Il self-hosting sostituisce tutto questo con la gestione autonoma degli aggiornamenti.

Vaultwarden distribuisce le correzioni di sicurezza come normali release. La versione 1.37.0, rilasciata il 24 luglio 2026, è quella corrente ad agosto 2026 e nelle note chiede agli utenti di aggiornare il prima possibile. Un'istanza configurata un anno fa e poi dimenticata esegue codice vecchio di un anno. Il tag latest, da solo, non è sufficiente: un container in esecuzione continua a usare l'immagine con cui è stato avviato finché non esegui docker compose pull e non lo ricrei. Configura aggiornamenti automatici su Ubuntu per i pacchetti dell'host e inserisci l'aggiornamento del container in un promemoria del calendario che leggerai davvero.

La conclusione che un lettore dovrebbe trarre è questa: la crittografia si basa sul design di Bitwarden ed è solida, mentre il rischio operativo ricade interamente su di te. Se applichi le patch e conservi un backup in un'altra posizione, un'istanza Vaultwarden su un VPS sotto il tuo controllo è un luogo ragionevole per conservare le password. Se queste due attività non verranno svolte, paga il servizio in hosting e dedica altrove la tua attenzione. Il confronto dettagliato delle funzionalità è disponibile in Vaultwarden a confronto con Bitwarden self-hosted.

Metti in sicurezza l’host sottostante il container

Vaultwarden è un processo in esecuzione su un sistema Linux e l’account root del sistema può leggere /vw-data indipendentemente dalla configurazione dell’applicazione. Esegui il container con un utente non privilegiato usando user: "1000:1000" nel file compose e assegna la proprietà della directory dei dati in modo coerente. Monta quindi in sola lettura tutto ciò che il container non deve modificare, usando :ro. Chiudi infine il punto di accesso principale: Messa in sicurezza di SSH su un VPS illustra l’accesso esclusivo tramite chiavi e la disabilitazione dell’autenticazione tramite password. È questo che blocca l’attacco più comune, che altrimenti potrebbe superare tutte le misure precedenti.

FAQ

Qualcuno può leggere le mie password se ruba il database di Vaultwarden?

Non direttamente. Ogni elemento del vault viene cifrato nel client con una chiave derivata dalla password principale, quindi db.sqlite3 contiene testo cifrato. L'attaccante ottiene immediatamente l'indirizzo email di ogni account, le impostazioni KDF, i metadati di accesso e del dispositivo, nonché i secret per l'autenticazione a due fattori nella tabella twofactor, archiviati senza cifratura perché il server deve calcolare il codice previsto. Può inoltre attaccare offline il testo cifrato del vault per tutto il tempo necessario. Per questo, la lunghezza della password principale è il fattore che determina l'esito.

Devo usare ADMIN_TOKEN o disabilitare completamente la pagina di amministrazione?

Disabilitala se possibile, perché la maggior parte delle istanze ne ha bisogno una sola volta per configurare SMTP e invitare gli utenti, ma non più in seguito. Per disabilitarla, non impostare né ADMIN_TOKENDISABLE_ADMIN_TOKEN, rimuovi qualsiasi chiave "admin_token" da config.json, quindi ricrea il container. Rimuovere soltanto la variabile d'ambiente non è sufficiente, perché le impostazioni scritte dalla pagina di amministrazione risiedono in config.json e hanno la precedenza. Se mantieni la pagina, archivia il token come hash Argon2 generato da vaultwarden hash invece di usare una stringa casuale in chiaro, e imposta ADMIN_RATELIMIT_MAX_BURST=3.

Il mio ADMIN_TOKEN è corretto, ma /admin lo rifiuta. Qual è il problema?

Quasi sempre si tratta dell'interpolazione di $. Una stringa PHC Argon2 contiene diversi caratteri $ e Docker Compose li espande come variabili all'interno di un blocco docker-compose.yml environment:. Di conseguenza, il container riceve un valore alterato anche se il file sembra corretto. Nel file compose raddoppia ogni $ trasformandolo in $$ oppure sposta il valore in un file .env racchiuso tra apici singoli, dove non è necessario alcun escaping. Ricrea il container al termine, perché un riavvio non applica le modifiche alle variabili d'ambiente.

Devo ancora aprire la porta 3012 per le notifiche?

No. Il supporto al traffico WebSocket sulla porta 3012 è stato rimosso in Vaultwarden 1.31.0, perché le notifiche sono state spostate sulla porta HTTP principale; inoltre, WEBSOCKET_ENABLED e WEBSOCKET_PORT vengono ignorati dalla versione 1.29.0. L'impostazione attuale è ENABLE_WEBSOCKET, che per impostazione predefinita è true. Chiudi la porta 3012 nel firewall e rimuovila dal file compose. Verifica poi che il reverse proxy inoltri gli header Upgrade e Connection, perché la sincronizzazione in tempo reale dipende ora da questi header.