Bitwarden ufficiale self-hosted parte da circa 2 GB di RAM e una manciata di container .NET. Vaultwarden, dopo la mia installazione di oggi su un container Proxmox, gira a 7,4 MiB di RAM con Web Vault, API REST, WebSocket e admin panel attivi. Differenza di due ordini di grandezza, stesse feature per quasi tutto ciò che serve a chi usa un password manager in famiglia. Davanti a un divario simile, la domanda interessante non è se installare Vaultwarden, ma
come architettare il setup in modo che resti banale da gestire fra dodici mesi: HTTPS che non rompa nulla, backup che funzioni davvero, admin panel che non diventi una porta sul retro lasciata aperta. Questa guida è il decalogo delle scelte che ho preso oggi sul mio CT 131, motivate una a una, con numeri misurati e riferimenti alla doc ufficiale di dani-garcia/vaultwarden. Dove l'ho fatto in modo diverso dalla wiki, lo dico.
Perché LXC + Docker e non una VM dedicata
Vaultwarden è un binary Rust che a regime occupa 7,4 MiB di RAM, scritto per essere drop-in compatibile con i client Bitwarden ufficiali. Un workload del genere in una VM completa è spreco puro: l'hypervisor ti porta via centinaia di MiB di kernel, systemd, journald e il resto del corredo, per servire un processo che potrebbe stare in cache L3. Un container LXC unprivileged su Proxmox gli dà l'isolamento di sicurezza che serve senza farti pagare un kernel separato, e con VM o LXC su Proxmox dipende dal carico: qui il carico è minimale e prevedibile, l'LXC vince a mani basse.
La scelta successiva — Docker dentro l'LXC — è meno ovvia. Si potrebbe compilare Vaultwarden da sorgente Rust e installarlo come servizio systemd, risparmiando l'overhead del runtime container. In pratica non vale la pena: l'immagine ufficiale vaultwarden/server:latest pesa 187 MB, è firmata dal manutentore upstream, viene aggiornata in tempo reale quando esce una release di sicurezza, e include già la versione corretta del Web Vault bundled. Compilare da sorgente vuol dire spendere mezz'ora di CPU al primo build, cinque minuti a ogni update, e doversi inventare un meccanismo per allineare la versione del Web Vault frontend con quella del server. Il container Docker ti regala questo allineamento gratis.
Per far girare Docker dentro LXC servono due feature attivate sul container Proxmox: nesting=1 (per i namespace annidati) e keyctl=1 (per il keyring del kernel che Docker usa per overlay2). Senza queste due flag il daemon parte ma poi crasha al primo
Iscriviti alla newsletter per ricevere i migliori articoli direttamente nella tua inbox.
Self-Hosting
Accesso remoto server casa senza aprire porte: NetBird lo fa gratis
Accesso remoto al tuo server di casa senza port forwarding né firewall aperto: NetBird usa WireGuard P2P e funziona gratis fino a 100 device.
docker run
con errori opachi sui mount. Le ho impostate prima di creare il CT, e ho dimensionato l'LXC su 1 core, 512 MB RAM, 8 GB di disco su pool ZFS mirror SSD. Sono numeri sovradimensionati per Vaultwarden ma comodi per non doverli toccare quando aggiungerò Caddy, lo script di backup e magari un secondo servizio leggero in futuro.
RisorsaRAM
Allocata al CT512 MB
Usata a regimeVaultwarden 7,4 MiB + Caddy 14,5 MiB = 22 MiB
% usata4,3%
RisorsaDisco
Allocata al CT8 GB
Usata a regimeDebian + Docker + immagini + dati = 990 MB
% usata12%
RisorsaCPU
Allocata al CT1 core
Usata a regimeidle, picchi <2% al login
% usatatrascurabile
RisorsaImage vaultwarden/server
Allocata al CT—
Usata a regime187 MB
% usata—
RisorsaImage caddy:2-alpine
Allocata al CT—
Usata a regime35,1 MB
% usata—
Risorsa
Allocata al CT
Usata a regime
% usata
RAM
512 MB
Vaultwarden 7,4 MiB + Caddy 14,5 MiB = 22 MiB
4,3%
Disco
8 GB
Debian + Docker + immagini + dati = 990 MB
12%
CPU
1 core
idle, picchi <2% al login
trascurabile
Image vaultwarden/server
—
187 MB
—
Image caddy:2-alpine
—
35,1 MB
—
Tradotto: il container intero — sistema operativo, runtime, password manager, reverse proxy, dati — sta in meno di 1 GB di disco e meno di 25 MiB di RAM. Per chi viene da Bitwarden ufficiale è un cambio di paradigma, non un'ottimizzazione marginale.
Web Vault 2026.4.1 servito direttamente dal binary Vaultwarden — primo accesso, vault ancora vuoto
HTTPS interno con Caddy: perché non puoi saltarlo
Il Web Vault di Vaultwarden non funziona via HTTP puro. Non è una raccomandazione ma un vincolo tecnico: il client web Bitwarden esegue cifratura lato browser tramite la SubtleCrypto API, che i browser moderni espongono solo in secure context — HTTPS o localhost. Lo dice esplicitamente il README upstream: "the web-vault requires the use of HTTPS and a secure context for the Web Crypto API". Al primo accesso da http://10.0.10.131 mi sono beccato un alert giallo proprio su questo, ed era prevedibile. Quindi reverse proxy con TLS, non c'è alternativa.
Per la rete interna ho scelto Caddy con tls internal. È la direttiva che fa firmare al modulo PKI integrato di Caddy un certificato self-signed con una propria CA root locale, senza dover passare da Let's Encrypt o da un dominio pubblico. Per un servizio che non deve essere raggiungibile da Internet è la scelta più pulita: zero dipendenze esterne, rinnovo automatico, zero rischi di rate-limit ACME. Per esporre Vaultwarden anche fuori casa esistono pattern molto diversi — se mai vorrai farlo, l'approccio sano resta separare l'esposizione pubblica con un reverse proxy dedicato come Pangolin in tunnel WireGuard, lasciando Caddy a gestire la sola tratta interna.
caddy
# Caddyfile minimale per Vaultwarden in rete interna
vaultwarden.lan {
tls internal
# API + Web Vault
reverse_proxy vaultwarden:80
# WebSocket per sync real-time fra device
reverse_proxy /notifications/hub vaultwarden:3012
}
Due dettagli operativi che la wiki accenna ma che è facile mancare. Primo: Caddy con tls internal non firma certificati per IP letterali. Mette nel SAN solo nomi DNS. Se provi a esporlo come https://10.0.10.131 la TLS handshake fallisce con un alert opaco. Va usato un hostname — io ho scelto vaultwarden.lan — e risolto via /etc/hosts del client o via DNS interno (Pi-hole, AdGuard, Blocky, Unbound: tanto la zona la gestisci tu). Secondo: la CA root che Caddy genera al primo avvio va importata nel browser una volta sola, altrimenti NET::ERR_CERT_AUTHORITY_INVALID ti chiude la porta in faccia. Il certificato sta dentro il container Caddy in /data/caddy/pki/authorities/local/root.crt; lo si copia fuori una volta e si distribuisce sui dispositivi familiari.
Il terzo dettaglio è il proxy del WebSocket. Vaultwarden espone HTTP sulla porta 80 ma le notifiche di sync real-time fra device viaggiano sulla porta 3012, su un endpoint separato /notifications/hub. Se Caddy gli proxa solo l'HTTP, il vault funziona ma quando aggiungi una password sul telefono non la vedi sul desktop fino al refresh manuale. Le due righe reverse_proxy /notifications/hub vaultwarden:3012 sopra risolvono il problema, ma è il tipo di dettaglio che scopri solo quando un familiare ti dice "non vedo le password che hai aggiunto ieri".
Admin panel: sigillarlo prima ancora di accenderlo
L'admin panel di Vaultwarden — raggiungibile su /admin una volta impostato ADMIN_TOKEN — è una superficie d'attacco di prima categoria. Da lì si gestiscono utenti, organizzazioni, configurazione SMTP, integrazione SSO, log e la diagnostica completa dell'istanza. Storicamente è stato anche il punto debole sfruttato in più di un advisory recente: nel gennaio 2025 una RCE critica (GHSA-h6cc-rc6q-23j4) e un CSRF (GHSA-f7r5-w49x-gxm3), a marzo 2026 un bypass 2FA su protected actions (CVE-2026-27801) corretto in 1.35.0. Tutte vulnerabilità chiuse, ma il messaggio è chiaro: questo endpoint va trattato come una console di management, non come una pagina web qualsiasi.
Tre regole non negoziabili. Uno: ADMIN_TOKEN in formato hash Argon2id PHC, mai in plaintext. La wiki ufficiale Enabling-admin-page documenta i parametri preset Bitwarden (m=64 MiB, t=3 iterations, p=4 threads) e ricorda che in Docker Compose ogni $ della stringa PHC va raddoppiato come $$ — sono cinque occorrenze tipiche, sbagliarne una invalida l'hash e l'admin smette di rispondere. Due: ADMIN_SESSION_LIFETIME va lasciato sui 20 minuti di default o accorciato, mai allungato. Tre: l'admin panel non va esposto pubblicamente, mai. Anche se l'hash è solido, riduci la superficie.
bash
# 1) genera un token random forte, 32 byte base64
openssl rand -base64 32 > admin_token_plain.txt
# 2) hash Argon2id con preset Bitwarden (m=65540 KiB ~ 64 MiB, t=3, p=4)
argon2 "$(cat admin_token_plain.txt)" -e -id -k 65540 -t 3 -p 4
# 3) copia l'output $argon2id$v=19$m=65540,t=3,p=4$... nel docker-compose.yml
# raddoppiando OGNI segno $ (es. $$argon2id$$v=19$$m=65540...)
# Il token plain in admin_token_plain.txt e' quello che digiterai in /admin
C'è poi un quarto comportamento operativo che cambia tutto: SIGNUPS_ALLOWED va flippato a false subito dopo aver creato il proprio account. Default upstream è true, che ha senso per il primo bootstrap ma diventa un buco aperto un minuto dopo. Lasciato attivo, chiunque raggiunga l'istanza — anche solo via LAN, anche solo per un test — può registrarsi e finire dentro il tuo Vaultwarden con un'identità sua. Ho verificato che dopo il flip l'API /identity/accounts/register ritorna 422, e nuovi utenti vanno aggiunti per invito esplicito dall'admin. Per un'istanza familiare è esattamente il comportamento che vuoi.
Diagnostics page dell'admin panel: tutti i check verdi sono il segnale che reverse proxy, WebSocket e DOMAIN sono allineati
Il pannello Diagnostics è la diagnostica più onesta che ho mai visto in un servizio self-hosted. Versione del server confrontata con l'ultima upstream, versione del Web Vault, backend DB rilevato, IP header che il proxy sta passando, raggiungibilità WebSocket, accesso a internet per il fetch delle favicon, match del DOMAIN. Tutti i check verdi è un'ottima sanity check post-deploy: se uno è giallo o rosso, il problema è quasi sempre nel reverse proxy o nell'.env, raramente nel binary Vaultwarden.
Quando deciderai di portare l'autenticazione fuori da master password e 2FA TOTP — perché in un homelab maturo ha senso — l'admin panel ha già la sezione SSO via OpenID Connect pronta a essere collegata. Non l'ho testata in questa sessione, ma è il punto naturale di integrazione con un IdP self-hosted, che ho documentato a parte parlando di SSO + 2FA con Authentik nel proprio homelab: stesso pattern, applicato a Vaultwarden invece che ai servizi web.
Backup: la verità scomoda dell'immagine senza sqlite3
Ogni guida online ti dice di usare sqlite3 .backup sull'Online Backup API per fare snapshot a caldo del database SQLite di Vaultwarden. È il pattern raccomandato dalla wiki Backing-up-your-vault e funziona benissimo — tranne che l'immagine ufficiale vaultwarden/server:latestnon include il binario sqlite3. È un'immagine slim by design. Provando docker exec vaultwarden sqlite3 ti rimbalza con "sqlite3: not found" e la guida si ferma a metà.
Hai due strade pulite. La prima — quella che ho usato — è il classico stop, tar, start della directory vw-data/. Niente sqlite3, niente backup online, ma cattura tutto: db.sqlite3, attachments, sends, config.json, le chiavi RSA. Sul mio setup single-user single-voce il downtime totale misurato è stato di 24 secondi end-to-end, container fermo incluso. Il tarball pesa 12 KB. Per dieci utenti familiari con cento voci a testa parliamo comunque di pochi MB e di un paio di minuti — tempi che alle 3 di notte da cron non disturbano nessuno.
bash
#!/bin/bash
# /usr/local/bin/vw-backup.sh — backup hot di vw-data
set -euo pipefail
BACKUP_DIR=/var/backups/vaultwarden
DATE=$(date +%Y%m%d-%H%M%S)
cd /opt/vaultwarden # dove sta docker-compose.yml + vw-data/
mkdir -p "$BACKUP_DIR"
docker compose stop vaultwarden
tar -czf "$BACKUP_DIR/vw-data-$DATE.tgz" vw-data
docker compose start vaultwarden
# retention: 30 backup, FIFO
ls -1t "$BACKUP_DIR"/vw-data-*.tgz | tail -n +31 | xargs -r rm
La seconda strada è più elegante e dalla 1.32.1 in poi è il pattern moderno della doc upstream: docker exec vaultwarden /vaultwarden backup usa il binary Vaultwarden stesso per emettere uno snapshot SQLite consistente senza fermare il servizio. Resta il fatto che questo backup contiene solo il DB: per attachments, sends, chiavi RSA e config.json devi comunque salvare la directory. Per questo nello script sopra preferisco il tar completo, e se ho bisogno di fare un dump aggiuntivo del solo DB lo faccio con il subcomando ufficiale come operazione complementare, non sostitutiva.
Confessione: la prima volta che ho scritto lo script di backup mi sono dimenticato di copiare il file fuori dal CT. Avevo backup giornalieri perfetti, dentro lo stesso container che dovevano salvare. Se quel CT fosse esploso — un brick del filesystem, un rollback ZFS sbagliato, un rm -rf distratto — avrei perso tutto, backup compresi. L'ho corretto un'ora dopo aggiungendo un job che spinge il tarball su un dataset ZFS separato e poi su un endpoint S3 offsite. La regola 3-2-1 vale anche per i password manager, soprattutto per loro.
Il discorso "dove vanno i backup" è una conversazione separata che ho trattato a parte ragionando sui pool ZFS, snapshot e disaster recovery per chi sta in homelab: stessa logica applicata al caso più piccolo possibile. Per Vaultwarden l'output dello script di sopra è qualcosa che si trasferisce con uno zfs send incrementale o un restic backup in pochi secondi. La disciplina conta, non lo strumento.
L'obiezione più seria: ne vale la pena rispetto a Bitwarden cloud?
Tutto questo ragionamento sull'architettura ha un'obiezione legittima: Bitwarden ufficiale offre un piano cloud gratuito con vault personale illimitato, sync su tutti i dispositivi, backup gestito, push notification mobili che funzionano davvero, e zero manutenzione. La master password non lascia mai il browser, il modello di sicurezza è zero-knowledge, e il provider non può leggere i dati anche volendo. Per la maggior parte degli utenti privati questo basta. Self-hostare un password manager aggiunge tre superfici di rischio nuove — il server, la rete, i backup — che il piano gratuito Bitwarden semplicemente non ha. È un'obiezione vera, va presa sul serio.
La controrisposta non è ideologica. Vaultwarden ha senso nel momento esatto in cui hai già un homelab funzionante con backup ZFS ricorrenti, monitoraggio centralizzato, reverse proxy HTTPS interno e una pipeline di update gestita. In quel contesto le tre superfici di rischio nuove non sono nuove: sono già coperte dalla disciplina che applichi a tutto il resto. Aggiungere Vaultwarden costa 22 MiB di RAM e cinque minuti di docker compose pull al mese. In cambio ottieni un controllo totale sul ciclo di vita dei tuoi segreti, sui parametri KDF, sulle policy di scadenza, sull'integrazione SSO futura. Hai anche, dato non trascurabile, un servizio che continua a funzionare quando l'autenticazione SSO di un provider cloud va giù per due ore — e succede.
Resta una limitazione architetturale onesta da dichiarare. Le push notification mobili di Vaultwarden — quelle che fanno apparire la richiesta 2FA sul telefono in tempo reale, per intenderci — funzionano solo registrando l'istanza al relay ufficiale push.bitwarden.com tramite PUSH_INSTALLATION_ID e PUSH_INSTALLATION_KEY ottenibili gratis da bitwarden.com/host. Chi vuole un setup 100% air-gapped non può avere push reali sui client mobile ufficiali — è una scelta architetturale di Bitwarden, non un limite di Vaultwarden. Per la maggior parte di chi self-hosta è un trade-off accettabile, ma è giusto saperlo prima di firmare.
Aggiornare Vaultwarden: la disciplina che decide tutto
L'ultima decisione architetturale è quella che ti tiene al sicuro nel medio periodo: una pipeline di update disciplinata. La 1.36.0 che ho installato oggi, rilasciata il 3 maggio 2026, chiude da sola sei advisory di sicurezza — incluse due CSRF nel flow SSO, un'enumerazione utenti e un SSRF sul fetch delle icone. Le release di Vaultwarden non sono mai cosmetiche: ogni minor porta fix di sicurezza che valgono l'aggiornamento.
CVE / GHSAGHSA-h6cc-rc6q-23j4
TipoRCE critica admin panel
Versione che chiude1.32.5 (gennaio 2025)
CVE / GHSAGHSA-f7r5-w49x-gxm3
TipoCSRF admin panel
Versione che chiude1.32.6
CVE / GHSAGHSA-w9f8-m526-h7fh
TipoCross-user cipher access via UUID
Versione che chiude1.33.x
CVE / GHSACVE-2026-27801
TipoBypass 2FA su protected actions
Versione che chiude1.34.4
CVE / GHSAGHSA-h265-g7rm-h337
TipoCross-collection auth
Versione che chiude1.35.x (febbraio 2026)
CVE / GHSAGHSA-pfp2-jhgq-6hg5 + altre 5
TipoSSO CSRF, enum utenti, SSRF icon
Versione che chiude1.36.0 (3 maggio 2026)
CVE / GHSA
Tipo
Versione che chiude
GHSA-h6cc-rc6q-23j4
RCE critica admin panel
1.32.5 (gennaio 2025)
GHSA-f7r5-w49x-gxm3
CSRF admin panel
1.32.6
GHSA-w9f8-m526-h7fh
Cross-user cipher access via UUID
1.33.x
CVE-2026-27801
Bypass 2FA su protected actions
1.34.4
GHSA-h265-g7rm-h337
Cross-collection auth
1.35.x (febbraio 2026)
GHSA-pfp2-jhgq-6hg5 + altre 5
SSO CSRF, enum utenti, SSRF icon
1.36.0 (3 maggio 2026)
Letta così, la storia delle ultime release dice una cosa sola: chi resta indietro di due o tre minor versioni accumula rischio reale, non teorico. Il pattern che ho impostato è banale — docker compose pull && docker compose up -d ogni primo del mese, oppure subito quando GitHub mi notifica un nuovo tag — ma è la disciplina che separa un setup robusto da uno che invecchia male. La 1.36.0 ha anche introdotto, per la prima volta, release immutable + attestation su GitHub: significa che ogni binary è firmato con provenance verificabile, e che downgrade silenziosi o supply chain attack diventano materialmente più difficili. Non è una feature flashy ma è il segnale che il progetto sta maturando lato security supply chain, e questo conta più di qualsiasi changelog.
L'admin panel mostra subito quali settings sono active e quali no — è il modo più veloce per fare audit del config
Una nota finale che vale tutta la sezione: la wiki upstream raccomanda esplicitamente di non usare vaultwarden/server:alpine sotto Docker, e di non mettere il container dietro versioni storiche di nginx/Apache senza supporto WebSocket completo. Sono i due errori che ricorrono nelle issue più frequentemente. Caddy 2 li gestisce out-of-the-box e l'immagine debian-based ufficiale è quella supportata. Fine della complicazione.
Riassumendo le decisioni che ho preso oggi: LXC unprivileged Proxmox + Docker Compose + immagine ufficiale debian-based + Caddy con tls internal + ADMIN_TOKEN Argon2id + SIGNUPS_ALLOWED a false dopo bootstrap + backup tar dell'intera vw-data verso storage offsite + update mensile disciplinato. Sette scelte, ognuna motivata da un vincolo tecnico o da una lesson learned reale. Nessuna è particolarmente brillante presa singolarmente. Insieme, fanno la differenza fra un password manager che funziona oggi e uno che funziona ancora bene fra due anni.
Vaultwarden a 7,4 MiB di RAM non è un esperimento da nerd. È quello che il password manager dovrebbe essere stato fin dall'inizio.