Backup e ripristino di Vaultwarden su VPS
Scopri come copiare un vault attivo con sqlite3 .backup, includere allegati, config.json e rsa_key e verificare il ripristino prima di un'emergenza.
Contenuto obbligatorio di un backup di Vaultwarden
Un backup di Vaultwarden è una copia dell'intera directory dei dati e il database al suo interno deve essere copiato nel modo corretto. Esegui sqlite3 db.sqlite3 ".backup out.sqlite3" invece di cp, perché la copia diretta di un database durante una scrittura può produrre un file che non si apre. Conserva anche i file presenti nella stessa directory: sono quelli che spesso vengono dimenticati.
In un'installazione Docker, la directory dei dati è quella montata su /data. Può essere un percorso sull'host oppure un volume con nome; la differenza tra bind mount e volumi con nome determina dove si trovano realmente i dati del vault sul disco. Questa directory contiene quanto segue.
db.sqlite3: tutti gli account, gli elementi di tutti i vault, le cartelle e le organizzazioni. Se perdi questo file, perdi il vault.db.sqlite3-waledb.sqlite3-shm: il write-ahead log (WAL) e il relativo indice nella memoria condivisa. Le scritture recenti restano qui finché SQLite non le integra nel file principale.attachments/: i file allegati dagli utenti agli elementi del vault, cifrati e organizzati in una directory per ogni elemento.sends/: i file associati ai link di Bitwarden Send.config.json: tutte le impostazioni salvate 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, perché Vaultwarden le scarica nuovamente quando necessario.
La base di dati di Vaultwarden è sicura? Cosa contiene realmente il file
Due comandi rispondono a questa domanda e puoi eseguirli entrambi subito.
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 in chiaro gli indirizzi email degli utenti. Il secondo stampa il nome di un elemento, con un risultato simile al seguente:
2.k9Qw1nQ0y7Yy2Xw==|E1r0J3l5s7d9f1g3h5j7k9==|Lm4nOp6qRs8tUv0wXy2zAb4cDe6fGh8i=I nomi degli elementi, i nomi utente, le password e le note vengono cifrati dal client prima dell'invio. Il server memorizza quindi testo cifrato che non può leggere. Il prefisso 2. identifica il tipo di cifratura di Bitwarden. Seguono un vettore di inizializzazione (IV), il testo cifrato e un MAC (codice di autenticazione del messaggio), tutti codificati in base64 e separati da |. La chiave per decifrare questi dati viene derivata dalla password principale dell'account, che non raggiunge mai il server in una forma utilizzabile. Questo aspetto è identico sia quando esegui Vaultwarden sia quando usi il server ufficiale, come spiega il confronto tra Vaultwarden e Bitwarden self-hosted.
Il resto della base di dati non è cifrato. Gli indirizzi email, i nomi degli account, i suggerimenti per le password e i codici di recupero dell'autenticazione a due fattori sono memorizzati in testo semplice, insieme a metadati come gli orari di creazione e l'organizzazione proprietaria di un elemento. Il file di backup è quindi a sua volta un segreto. Chiunque ne possieda una copia può sapere chi sono gli utenti e attaccare offline i dati cifrati, alla velocità consentita dal proprio hardware. Questo aspetto determina le regole di archiviazione descritte più avanti: la copia viene cifrata prima di lasciare il server. Il token amministrativo è l'altra parte dello stesso problema e la procedura di hardening per Vaultwarden self-hosted tratta entrambi gli aspetti.
Perché copiare db.sqlite3 mentre Vaultwarden è in esecuzione non costituisce un backup
Per impostazione predefinita, Vaultwarden esegue SQLite in modalità WAL (ENABLE_DB_WAL=true). Un'operazione di scrittura viene prima registrata in db.sqlite3-wal e solo un checkpoint la incorpora in db.sqlite3. Se copi soltanto db.sqlite3, ottieni il database nello stato dell'ultimo checkpoint. Di conseguenza, una password salvata dieci minuti prima può mancare nell'archivio senza che venga generato alcun avviso.
Copiare tutti e tre i file con cp non risolve il problema. Le copie vengono create in momenti leggermente diversi, quindi il WAL salvato può descrivere versioni delle pagine che non corrispondono più al file principale copiato. SQLite tenta quindi di recuperare un file usando l'altro, ma il risultato è errato. Il problema viene rilevato molto più tardi:
Error: database disk image is malformed.backup evita questo problema perché usa l'API Online Backup di SQLite, che la documentazione di SQLite indica come metodo per copiare un database potenzialmente in uso. Legge le pagine acquisendo un blocco di lettura e ricomincia se un processo di scrittura modifica il file durante l'operazione. Il file scritto su disco rappresenta quindi un unico stato coerente del database.
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 separata. Qualsiasi altro risultato indica che la copia non è utilizzabile: non conservarla e non eliminare quella precedente. L'intera sequenza viene eseguita su un server in uso, quindi nessuno viene disconnesso e nessun container viene riavviato.
Lo strumento sqlite3 non è presente nel container Vaultwarden. L'immagine è basata su debian:trixie-slim con ca-certificates, curl, libmariadb3, libpq5 e openssl, quindi docker exec vaultwarden sqlite3 ... non riesce e restituisce:
exec: "sqlite3": executable file not found in $PATHEseguirlo sull'host, usando il percorso montato, come fanno i comandi precedenti. Se i dati si trovano in un volume con nome, docker volume inspect <name> stampa il percorso dell'host sotto /var/lib/docker/volumes/.
A partire dalla versione 1.32.1, Vaultwarden include anche un comando di backup integrato. Sul server:
docker exec -it vaultwarden /vaultwarden backupIl comando esegue VACUUM INTO e scrive db_YYYYMMDD_HHMMSS.sqlite3 nella directory dei dati. Questo comporta due conseguenze. La copia viene creata accanto all'originale sullo stesso disco, quindi costituisce un passaggio di staging e non ancora un backup. Inoltre, funziona solo con SQLite: con MariaDB o PostgreSQL si interrompe restituendo The database type is not SQLite. Backups only works for SQLite databases.
I file che spesso vengono dimenticati
attachments/ contiene testo cifrato con nomi opachi. La riga del database relativa a ogni allegato contiene il nome cifrato del file e il materiale crittografico necessario al client per decrittarlo. Senza il database, gli allegati sono dati illeggibili. Senza gli allegati, il database contiene elementi i cui download falliscono. Eseguire il backup di entrambi nella stessa operazione.
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 può causare problemi in entrambi i sensi: il ripristino di un vecchio config.json sovrascrive senza segnalarlo le impostazioni del file compose, e il file stesso contiene dati sensibili perché può includere la password SMTP e il token di amministrazione. Salvare il token come stringa PHC (password hashing competition) Argon2id, non in testo normale. docker run --rm -it vaultwarden/server /vaultwarden hash ne genera una.
rsa_key.pem firma i JSON Web Token (JWT) che mantengono connessi i client. Se il file manca all'avvio, Vaultwarden genera una nuova chiave. Di conseguenza, tutti i token firmati con la chiave precedente non vengono più convalidati e tutti i client vengono disconnessi. Il contenuto del vault non viene perso, perché è cifrato con chiavi derivate dalla password principale. Il ripristino del file della chiave evita la disconnessione di massa.
sends/ contiene i file associati ai link Send. La loro assenza interrompe questi download e non causa altri problemi.
Inserisci tutto in uno 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"Salva il file come /usr/local/sbin/vw-backup.sh, rendilo eseguibile con chmod 700 ed eseguilo come root. La riga test esegue un controllo effettivo: sqlite3 restituisce 0 anche quando PRAGMA integrity_check segnala una corruzione, quindi confrontare l'output con ok è ciò che fa fallire lo script in caso di copia non valida. set -euo pipefail interrompe quindi l'intera procedura, invece di consentire a tar di creare un archivio apparentemente corretto attorno a un database danneggiato.
L'ultimo tar -tzf elenca i dati effettivamente acquisiti. Leggilo la prima volta. Cerca ./db.sqlite3, ./rsa_key.pem, ./config.json e ./attachments/, verificando che ./db.sqlite3-wal non sia presente. Eseguilo ogni notte con un servizio e un timer systemd invece di cron se vuoi journalctl nell'output e un'unità che segnali gli errori.
Verificare il backup ripristinandolo in una directory di prova
Un backup non verificato è solo un'ipotesi. Il ripristino in una directory di prova richiede un minuto e non modifica nulla nell'ambiente attivo.
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 importanti. integrity_check stampa ok. Il numero di utenti corrisponde al numero di account conosciuti. Il numero di cifrari è vicino al valore dell'ambiente attivo indicato da sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select count(*) from ciphers;" e non è mai zero in un vault utilizzato. La directory degli allegati ha all'incirca le dimensioni previste; questo controllo può essere saltato se nessuno carica allegati. Eseguire quindi sudo rm -rf /tmp/vw-check, perché quella directory contiene ora una seconda copia di tutto.
Quando si ripristina una directory di dati copiata manualmente, eliminare db.sqlite3-wal e db.sqlite3-shm prima di avviare il server. In caso contrario, SQLite tenterà di recuperare il database ripristinato usando un log appartenente a un'altra copia del database, corrompendo così un database arrivato integro. Gli archivi prodotti dallo script precedente non contengono questi file, perché .backup scrive un database completo.
Ripristino sul server
Eseguire questi comandi sul proprio server, con il container arrestato. Vaultwarden non deve scrivere mentre la directory 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 vaultwardenchown deve specificare l'utente con cui viene eseguito il container. L'immagine standard viene eseguita come root, quindi root:root è corretto, a meno che nel file compose non sia stato impostato user:. In tal caso, usare il relativo uid e gid. Se il server non può scrivere nella directory dei dati, la pagina di accesso viene visualizzata ma ogni richiesta fallisce. I log lo confermano.
Un avvio corretto termina con la riga Rocket:
[INFO] Rocket has launched from http://0.0.0.0:80Accedere quindi da un browser, aprire un elemento e scaricare un allegato. Se l'accesso funziona ma il download degli allegati fallisce, significa che l'archivio contiene il database ma non attachments/. Conservare data.old.* finché tutti i controlli non sono completati, quindi eliminarlo. Per eseguire il rollback, ripetere gli stessi tre passaggi scambiando le directory.
Se i percorsi non corrispondono a quelli riportati qui, la guida all'installazione di Vaultwarden su un VPS mostra il file compose presupposto da questi comandi.
Dove non conservare il backup
- Non sullo stesso disco della directory dei dati. Un volume guasto elimina entrambe le copie, così come un singolo
rm -rfsul percorso errato. - Non sullo stesso server, nemmeno su un secondo volume. Un attaccante che ottiene l'accesso a root raggiunge i backup nella stessa sessione.
- Non nell'object storage senza crittografia, perché l'archivio contiene indirizzi email, suggerimenti per le password, codici di recupero e ciphertext del vault che possono essere attaccati offline.
- Non soltanto negli snapshot del provider. Consentono un ripristino rapido, quindi è utile averli, ma si trovano nello stesso account del server: un problema dell'account li compromette insieme al server.
Una copia offsite è il caso d'uso di restic, perché un repository restic viene crittografato sulla macchina prima del caricamento. Sul 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 --pruneIndica a restic la directory dell'archivio, non la directory dei dati attivi, in modo che venga caricata la copia coerente già verificata. Conserva la password del repository in un luogo diverso dal server che protegge: se perdi la password, gli snapshot diventano illeggibili, per progettazione. Quando lo storage lo consente, assegna al server credenziali che permettano di scrivere ma non di eliminare, così la compromissione del server non può cancellare la propria cronologia. Configurazione dei backup restic su un VPS descrive in dettaglio il repository e la pianificazione, mentre restic a confronto con BorgBackup illustra la scelta se non l'hai ancora effettuata.
Testare il ripristino secondo una pianificazione
Scegli un giorno al mese. Copia lo snapshot più recente in una directory temporanea con restic restore latest --tag vaultwarden --target /tmp/vw-check, esegui lo stesso PRAGMA integrity_check, ripeti gli stessi conteggi delle righe, quindi annota la data e i conteggi. Un backup che non è stato ripristinato per sei mesi è un backup di cui non conosci lo stato. Ne scopri lo stato durante un'interruzione del servizio, cioè nel momento peggiore.
Una volta all'anno, esegui la procedura completa. Avvia un secondo container Vaultwarden su una porta libera con la directory dei dati ripristinata e accedi con un account reale. Questo verifica dall'inizio alla fine il percorso della password principale, cosa che nessun conteggio delle righe può fare. restic check --read-data-subset=10% secondo la stessa pianificazione verifica che i dati archiviati siano leggibili e non soltanto elencati.
FAQ
Posso copiare db.sqlite3 con cp mentre Vaultwarden è in esecuzione?
No. Vaultwarden usa SQLite in modalità WAL, quindi le scritture recenti si trovano in db.sqlite3-wal e non sono ancora presenti in db.sqlite3. Una cp del solo file principale le perde senza segnalazioni, mentre la copia separata dei due file può produrre una coppia non coerente, con errori che emergono solo in seguito come Error: database disk image is malformed. Usa invece sqlite3 /path/db.sqlite3 ".backup '/path/out.sqlite3'". Questo comando usa l'API Online Backup di SQLite e produce un unico file coerente mentre il server continua a rispondere alle 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 Send vengono scritti quando un utente ne carica uno, quindi un file aggiunto tra la copia del database e tar potrebbe non essere incluso nell'archivio di quella notte. Nel caso peggiore, perderesti un solo allegato. Se pochi secondi di inattività non sono un problema, docker compose stop prima dello script e docker compose start dopo lo script eliminano anche questo rischio.
Cosa succede se ripristino senza i file rsa_key?
Vaultwarden genera una nuova chiave all'avvio. Questa chiave firma i JSON Web Token (JWT) che mantengono attive le sessioni, quindi tutti i token esistenti non vengono più convalidati e tutti i client vengono disconnessi; dovranno eseguire nuovamente l'accesso. I contenuti del vault non vengono modificati, perché sono cifrati con chiavi derivate dalla password principale di ciascun utente e non con la chiave RSA. Ripristina rsa_key.pem insieme al resto della directory dei dati e nessuno noterà il ripristino.
L'archivio di backup è sicuro da caricare così com'è in un object storage?
No. I nomi degli elementi, le password e le note sono cifrati, ma gli indirizzi email, i nomi degli account, gli indizi delle password e i codici di recupero dell'autenticazione a due fattori sono testo in chiaro nel database. Inoltre, un attaccante offline può tentare di decifrare il testo cifrato senza limiti di velocità imposti dal server. Cifra l'archivio prima che lasci la macchina. Un repository restic esegue questa operazione automaticamente; gpg --symmetric --cipher-algo AES256 vw-20260805-030000.tar.gz produce invece un unico file cifrato che puoi caricare su qualsiasi storage.
Come eseguo il backup di Vaultwarden con PostgreSQL o MariaDB?
I passaggi per SQLite non si applicano e il comando integrato restituisce The database type is not SQLite. Backups only works for SQLite databases. Esegui il dump del database con lo strumento nativo, pg_dump o mysqldump, e mantieni invariata ogni altra regola. Il dump deve essere incluso in un unico archivio insieme a attachments/, sends/, config.json e ai file rsa_key. Tutti questi elementi devono essere acquisiti nella stessa esecuzione, cifrati e archiviati in una posizione diversa dal server che li ha generati.