Backup e ripristino Vaultwarden su VPS: guida pratica
Impara a eseguire il backup sicuro di Vaultwarden usando sqlite3 .backup. Proteggi il database, i file rsa_key, config.json e gli allegati per un ripristino garantito.
Cosa deve contenere un backup di Vaultwarden
Un backup di Vaultwarden è una copia dell'intera cartella dati; il database al suo interno deve essere copiato correttamente. Esegui sqlite3 db.sqlite3 ".backup out.sqlite3" invece di cp, poiché una copia semplice di un database in fase di scrittura può generare un file non apribile. Successivamente, conserva i file adiacenti, un passaggio che spesso viene dimenticato.
In un'installazione Docker, la cartella dati corrisponde a quella montata su /data. Si tratta di un percorso sull'host o di un volume denominato, e la differenza tra bind mount e volumi denominati determina dove risiedono fisicamente i dati del vault. Ecco cosa contiene:
db.sqlite3: ogni account, ogni elemento del vault, ogni cartella e ogni organizzazione. Perdere questo file significa perdere il vault.db.sqlite3-waledb.sqlite3-shm: il write-ahead log (WAL) e il suo indice di memoria condivisa. Le scritture recenti risiedono qui finché SQLite non le integra nel file principale.attachments/: i file allegati dagli utenti agli elementi del vault, criptati, organizzati in una directory per ogni elemento.sends/: i file associati ai link di Bitwarden Send.config.json: ogni impostazione salvata dalla pagina di amministrazione.rsa_key.pem, oltre arsa_key.derersa_key.pub.dernelle installazioni meno recenti: la chiave che firma i token di accesso.icon_cache/: le icone dei siti web scaricate. Questa è l'unica directory che puoi omettere, poiché Vaultwarden le recupera nuovamente su richiesta.
Il mio database Vaultwarden è sicuro? Cosa contiene realmente il file
Due comandi rispondono a questa domanda ed è possibile eseguirli entrambi immediatamente.
sudo apt update && sudo apt install -y sqlite3
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select email from users;"
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select name from ciphers limit 1;"Il primo stampa gli indirizzi email dei tuoi utenti in chiaro. Il secondo stampa il nome di un elemento, che appare in questo modo:
2.k9Qw1nQ0y7Yy2Xw==|E1r0J3l5s7d9f1g3h5j7k9==|Lm4nOp6qRs8tUv0wXy2zAb4cDe6fGh8i=I nomi degli elementi, i nomi utente, le password e le note vengono crittografati dal client prima dell'invio, pertanto il server memorizza testo cifrato che non è in grado di leggere. Il prefisso 2. indica il tipo di crittografia di Bitwarden, seguito da un vettore di inizializzazione (IV), dal testo cifrato e da un MAC (message authentication code), ciascuno in base64 e separato da |. La chiave che decrittografa il contenuto deriva dalla master password dell'account, che non raggiunge mai il server in una forma utilizzabile. Questa parte è identica sia che si utilizzi Vaultwarden sia che si utilizzi il server ufficiale, come approfondito in il confronto tra Vaultwarden e Bitwarden self-hosted.
Il resto del database non è crittografato. Gli indirizzi email, i nomi degli account, i suggerimenti per le password e i codici di recupero per l'autenticazione a due fattori sono memorizzati in chiaro, insieme a metadati come le date di creazione e l'organizzazione proprietaria di un elemento. Di conseguenza, il file di backup è esso stesso un segreto. Chiunque ne entri in possesso scopre chi sono i tuoi utenti e può attaccare i blob crittografati offline alla velocità consentita dal proprio hardware. Questo singolo fatto determina le regole di archiviazione descritte più avanti: la copia viene crittografata prima di lasciare il server.
Perché copiare db.sqlite3 mentre Vaultwarden è in esecuzione non costituisce un backup
Vaultwarden esegue SQLite in modalità WAL per impostazione predefinita (ENABLE_DB_WAL=true). Una scrittura viene salvata prima in db.sqlite3-wal e solo un checkpoint la integra in db.sqlite3. Copiare db.sqlite3 da solo significa ottenere il database aggiornato all'ultimo checkpoint; una password salvata dieci minuti prima potrebbe mancare nel tuo archivio senza alcun avviso.
Nemmeno copiare tutti e tre i file con cp risolve il problema. Le copie vengono effettuate in momenti leggermente diversi, quindi il file WAL salvato potrebbe descrivere versioni di pagine che non corrispondono più al file principale salvato. SQLite tenta quindi di eseguire il ripristino incrociato e il risultato è corrotto. Te ne accorgerai molto più tardi:
Error: database disk image is malformed.backup evita questo problema perché utilizza l'API di backup online di SQLite, documentata da SQLite come il metodo corretto per copiare un database in uso attivo. Legge le pagine sotto un blocco in lettura e ricomincia l'operazione se un processo di scrittura modifica il file sottostante, garantendo che ciò che viene scritto su disco rappresenti un singolo momento coerente.
Eseguire la copia del database con sqlite3 .backup
sudo apt update && sudo apt install -y sqlite3
sudo install -d -m 700 /var/backups/vaultwarden
OUT=/var/backups/vaultwarden/db-$(date '+%Y%m%d-%H%M').sqlite3
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 ".backup '$OUT'"
sudo sqlite3 "$OUT" "PRAGMA integrity_check;"L'ultimo comando stampa ok su una riga dedicata. Qualsiasi altro output indica che la copia non è utilizzabile; pertanto, non conservarla e non eliminare quella precedente. L'intera sequenza viene eseguita su un server attivo, quindi nessun utente viene disconnesso e nessun container viene riavviato.
Lo strumento sqlite3 non è presente all'interno del container Vaultwarden. L'immagine è basata su debian:trixie-slim con ca-certificates, curl, libmariadb3, libpq5 e openssl, motivo per cui docker exec vaultwarden sqlite3 ... fallisce con:
exec: "sqlite3": executable file not found in $PATHEseguirlo invece sull'host puntando al percorso montato, come indicato nei comandi precedenti. Se i dati risiedono in un volume denominato, docker volume inspect <name> stampa il percorso dell'host sotto /var/lib/docker/volumes/.
Vaultwarden include un proprio comando di backup dalla versione 1.32.1. Sul proprio server:
docker exec -it vaultwarden /vaultwarden backupIl comando esegue VACUUM INTO e scrive db_YYYYMMDD_HHMMSS.sqlite3 nella cartella dei dati. Si notino due aspetti. La copia viene salvata accanto all'originale sullo stesso disco, quindi si tratta di una fase di staging e non ancora di un backup definitivo. Inoltre, la funzione è limitata a SQLite: se si utilizza MariaDB o PostgreSQL, il processo si interrompe con The database type is not SQLite. Backups only works for SQLite databases.
I file che vengono dimenticati
attachments/ contiene dati cifrati con nomi opachi. La riga del database per ogni allegato riporta il nome del file cifrato e il materiale crittografico necessario al client per decifrarlo. Gli allegati senza il database sono rumore illeggibile, mentre un database senza gli allegati fornisce agli utenti elementi il cui download fallisce. Esegui il backup di entrambi nella stessa sessione.
config.json contiene tutto ciò che è stato salvato dalla pagina di amministrazione e i suoi valori hanno la precedenza sulle variabili d'ambiente corrispondenti. Questo è un'arma a doppio taglio: ripristinare un vecchio config.json sovrascrive silenziosamente le impostazioni nel file compose, e il file stesso è sensibile poiché può contenere la password SMTP e il token di amministrazione. Memorizza tale token come stringa Argon2id PHC (password hashing competition) anziché in testo semplice. docker run --rm -it vaultwarden/server /vaultwarden hash ne genera una per te.
rsa_key.pem firma i JSON web tokens (JWT) che mantengono i client autenticati. Se il file manca all'avvio, Vaultwarden genera una nuova chiave, quindi ogni token firmato con quella vecchia smette di essere valido e tutti i client vengono disconnessi. Il contenuto del Vault sopravvive, poiché è cifrato con chiavi derivate dalla master password. Ripristinare il file della chiave evita la disconnessione di massa.
sends/ contiene i file dietro i link di Send. La loro assenza interrompe solo quei download e nient'altro.
Inserire l'intera procedura in un unico script
#!/bin/bash
set -euo pipefail
DATA=/opt/vaultwarden/data
DEST=/var/backups/vaultwarden
STAMP=$(date '+%Y%m%d-%H%M%S')
STAGE=$(mktemp -d /tmp/vw-stage.XXXXXX)
install -d -m 700 "$DEST"
sqlite3 "$DATA/db.sqlite3" ".backup '$STAGE/db.sqlite3'"
test "$(sqlite3 "$STAGE/db.sqlite3" 'PRAGMA integrity_check;')" = "ok"
cp -a "$DATA"/rsa_key* "$STAGE/"
for extra in config.json attachments sends; do
if [ -e "$DATA/$extra" ]; then cp -a "$DATA/$extra" "$STAGE/"; fi
done
tar -C "$STAGE" -czf "$DEST/vw-$STAMP.tar.gz" .
chmod 600 "$DEST/vw-$STAMP.tar.gz"
rm -rf "$STAGE"
tar -tzf "$DEST/vw-$STAMP.tar.gz"Salvare il file come /usr/local/sbin/vw-backup.sh, renderlo chmod 700 ed eseguirlo come root. La riga test esegue l'operazione principale: sqlite3 restituisce il codice di uscita 0 anche quando PRAGMA integrity_check segnala corruzione, pertanto il confronto dell'output con ok è ciò che trasforma una copia danneggiata in uno script fallito. set -euo pipefail interrompe quindi ogni operazione, evitando che tar crei un archivio integro attorno a un database corrotto.
Il comando tar -tzf finale elenca ciò che è stato effettivamente acquisito. Verificare l'output al primo avvio. È necessario controllare la presenza di ./db.sqlite3, ./rsa_key.pem, ./config.json e ./attachments/, e l'assenza di ./db.sqlite3-wal. Eseguire lo script ogni notte tramite un servizio e un timer systemd anziché cron, se si desidera un output journalctl e un'unità che segnali eventuali errori.
Verifica del backup tramite ripristino in una directory temporanea
Un backup non testato è solo una supposizione. Il ripristino in una directory temporanea richiede un minuto e non altera i dati in produzione.
sudo install -d -m 700 /tmp/vw-check
sudo tar -C /tmp/vw-check -xzf /var/backups/vaultwarden/vw-20260805-030000.tar.gz
ls -l /tmp/vw-check
sudo sqlite3 /tmp/vw-check/db.sqlite3 "PRAGMA integrity_check;"
sudo sqlite3 /tmp/vw-check/db.sqlite3 "select count(*) from users;"
sudo sqlite3 /tmp/vw-check/db.sqlite3 "select count(*) from ciphers;"
sudo du -sh /tmp/vw-check/attachmentsQuattro risultati sono rilevanti. integrity_check stampa ok. Il numero di utenti deve corrispondere al numero di account noti. Il numero di cifrari deve essere vicino al valore rilevato dal sistema in produzione tramite sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select count(*) from ciphers;" e non deve mai essere zero per un vault in uso. La dimensione della directory degli allegati deve essere approssimativamente quella prevista; questo controllo può essere omesso se non vengono caricati allegati. Successivamente, esegui sudo rm -rf /tmp/vw-check, poiché quella directory contiene ora una seconda copia di tutti i dati.
Una regola fondamentale quando si ripristina una cartella di dati copiata manualmente: elimina db.sqlite3-wal e db.sqlite3-shm prima di avviare il server. In caso contrario, SQLite tenterà di recuperare il database ripristinato utilizzando un log appartenente a una copia diversa, corrompendo un database che era arrivato integro. Gli archivi prodotti dallo script sopra indicato non contengono mai questi file, poiché .backup scrive un database completo.
Ripristino sul server
Queste operazioni vanno eseguite sul proprio server, con il container arrestato. Vaultwarden non deve scrivere nel file system mentre la cartella dei dati viene modificata.
cd /opt/vaultwarden
docker compose stop vaultwarden
sudo mv data data.old.$(date '+%Y%m%d-%H%M%S')
sudo install -d -m 700 data
sudo tar -C data -xzf /var/backups/vaultwarden/vw-20260805-030000.tar.gz
sudo chown -R root:root data
docker compose start vaultwarden
docker compose logs --tail 20 vaultwardenIl comando chown deve specificare l'utente con cui viene eseguito il container. L'immagine standard viene eseguita come root, quindi root:root è corretto a meno che non sia stato impostato user: nel file compose; in tal caso, utilizzare quel uid e gid. Se il server non dispone dei permessi di scrittura sulla cartella dei dati, la pagina di login rifiuterà ogni richiesta e i log riporteranno l'errore.
Un avvio corretto termina con la riga Rocket:
[INFO] Rocket has launched from http://0.0.0.0:80Successivamente, effettuare il login da un browser, aprire un elemento e scaricare un allegato. Se il login funziona ma il download degli allegati fallisce, significa che l'archivio conteneva il database ma non attachments/. Conservare data.old.* finché tutte le verifiche non sono completate, quindi eliminarlo. Il rollback segue gli stessi tre passaggi, invertendo le directory.
Se i percorsi utilizzati non corrispondono a quelli indicati, la guida all'installazione di Vaultwarden su VPS mostra il file compose su cui si basano questi comandi.
Dove non archiviare i backup
- Non sullo stesso disco della cartella dati. Un volume guasto compromette entrambe le copie, così come un
rm -rfeseguito sul percorso errato. - Non sullo stesso server, nemmeno su un secondo volume. Un utente malintenzionato che ottiene i privilegi di root accede ai backup nella stessa sessione.
- Non in uno storage a oggetti senza crittografia, poiché l'archivio contiene indirizzi email, suggerimenti per le password, codici di recupero e il testo cifrato del vault, che possono essere attaccati offline.
- Non esclusivamente negli snapshot del provider. Sono utili per un ripristino rapido, ma risiedono nello stesso account del server; un problema all'account renderebbe inaccessibili anche quelli.
Una copia offsite è lo scenario ideale per restic, poiché un repository restic viene crittografato sulla macchina prima di qualsiasi caricamento. Sul tuo server:
sudo apt install -y restic
export RESTIC_REPOSITORY=s3:https://s3.example.com/vaultwarden-backups
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic init
restic backup /var/backups/vaultwarden --tag vaultwarden
restic snapshots --tag vaultwarden
restic forget --tag vaultwarden --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --pruneIndirizza restic verso la directory di archivio e non verso la cartella dei dati attivi, in modo che ciò che viene caricato sia la copia coerente che hai già verificato. Conserva la password del repository in un luogo diverso dal server che protegge: se perdi quella password, gli snapshot diventano illeggibili, per design. Laddove lo storage lo supporti, fornisci al server credenziali che consentano la scrittura ma non l'eliminazione, in modo che una compromissione della macchina non possa cancellare la propria cronologia. Configurazione dei backup con restic su una VPS tratta in modo completo il repository e la pianificazione, mentre confronto tra restic e BorgBackup analizza la scelta se non l'hai ancora effettuata.
Pianificare il test del ripristino
Scegliere un giorno al mese. Estrarre l'ultimo snapshot in una directory temporanea con restic restore latest --tag vaultwarden --target /tmp/vw-check, eseguire lo stesso PRAGMA integrity_check, verificare il conteggio delle righe e annotare data e risultati. Un backup che non viene ripristinato da sei mesi è un backup di cui non si conosce lo stato; scoprirlo durante un'interruzione del servizio è lo scenario peggiore.
Una volta all'anno, eseguire la procedura completa. Avviare un secondo container Vaultwarden su una porta libera utilizzando la cartella dei dati ripristinata e accedere con un account reale. Questa operazione verifica l'intero percorso della master password, cosa che il semplice conteggio delle righe non può fare. restic check --read-data-subset=10%, eseguito con la stessa frequenza, verifica che i dati archiviati siano leggibili e non solo presenti nell'elenco.
FAQ
Posso copiare db.sqlite3 con cp mentre Vaultwarden è in esecuzione?
No. Vaultwarden esegue SQLite in modalità WAL, quindi le scritture recenti risiedono in db.sqlite3-wal e non sono ancora presenti in db.sqlite3. Una cp del solo file principale comporta la perdita silenziosa di tali dati, e copiare i due file separatamente può generare una coppia non corrispondente che causerà Error: database disk image is malformed in futuro. Usa invece sqlite3 /path/db.sqlite3 ".backup '/path/out.sqlite3'". Questo comando utilizza l'API di backup online di SQLite e produce un file coerente mentre il server continua a gestire le richieste.
Devo arrestare il container Vaultwarden per eseguire un backup?
No, ed è proprio questo il vantaggio di .backup. La copia del database è sicura su un server in esecuzione. Gli allegati e i file inviati vengono scritti nel momento in cui l'utente li carica, quindi un file aggiunto tra la copia del database e il tar potrebbe non essere incluso nell'archivio della giornata, con una perdita massima di un singolo allegato. Se alcuni secondi di inattività non rappresentano un problema, docker compose stop prima dello script e docker compose start dopo di esso eliminano anche questo rischio.
Cosa succede se effettuo un ripristino senza i file rsa_key?
Vaultwarden genera una nuova chiave all'avvio. Tale chiave firma i token JSON web (JWT) che mantengono attive le sessioni; di conseguenza, ogni token esistente smette di essere valido, tutti i client vengono disconnessi e devono autenticarsi nuovamente. Il contenuto del vault non subisce alterazioni, poiché è crittografato con chiavi derivate dalla master password di ciascun utente e non dalla chiave RSA. Ripristina rsa_key.pem insieme al resto della cartella dati e nessuno si accorgerà del ripristino.
È sicuro caricare l'archivio di backup su uno storage a oggetti così com'è?
No. I nomi degli elementi, le password e le note sono cifrati, ma gli indirizzi email, i nomi degli account, i suggerimenti per le password e i codici di recupero a due fattori sono in chiaro nel database; un attaccante offline potrebbe forzare il testo cifrato senza limiti di tempo. Crittografa l'archivio prima che lasci la macchina. Un repository restic gestisce questo aspetto, e gpg --symmetric --cipher-algo AES256 vw-20260805-030000.tar.gz produce un singolo file crittografato che può essere inviato a qualsiasi storage.
Come posso eseguire il backup di Vaultwarden su PostgreSQL o MariaDB?
Le procedure per SQLite non sono applicabili e il comando integrato restituirà The database type is not SQLite. Backups only works for SQLite databases. Esegui il dump del database con il relativo strumento nativo, pg_dump o mysqldump, e mantieni valide tutte le altre regole. Il dump deve essere incluso in un unico archivio insieme a attachments/, sends/, config.json e ai file rsa_key, acquisiti nella stessa sessione, crittografati e conservati in una posizione diversa dal server che li ha generati.