How to backup and restore Vaultwarden for VPS
Learn how to backup Vaultwarden safely using sqlite3 .backup command. Don't just copy files. You must include db.sqlite3, attachments, config.json and rsa_key to restore well.
Wetin Vaultwarden backup must get inside
Vaultwarden backup na copy of your full data folder, and you must copy the database wey dey inside am correct way. Run sqlite3 db.sqlite3 ".backup out.sqlite3" instead of cp, because if you just copy database wey dey write data at that moment, the file fit corrupt and e no go open. After that, make sure you keep the files wey dey beside am, na this part plenty pipo dey forget.
For Docker install, the data folder na wetin you mount for /data. E fit be path for host or named volume, and the difference between bind mounts and named volumes go show you exactly where your vault dey for disk. Na these things e dey hold:
db.sqlite3: every account, every vault item, every folder, and every organisation. If you lose this file, your vault don go.db.sqlite3-walanddb.sqlite3-shm: the write-ahead log (WAL) and the shared memory index. Recent writes dey stay here until SQLite move dem enter the main file.attachments/: the files wey users attach to vault items, dem dey encrypted, and e dey one directory per item.sends/: the files wey dey behind Bitwarden Send links.config.json: every setting wey you save from the admin page.rsa_key.pem, plusrsa_key.derandrsa_key.pub.derfor older installs: the key wey dey sign login tokens.icon_cache/: website icons wey you download. You fit skip this directory if you like, because Vaultwarden go fetch dem again when e need dem.
My Vaultwarden database dey safe? Wetin the file really get inside
Two commands go answer this question, and you fit run both of dem right now.
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;"The first one go print your users' email addresses as plain text. The second one go print one item name, and e go look like this:
2.k9Qw1nQ0y7Yy2Xw==|E1r0J3l5s7d9f1g3h5j7k9==|Lm4nOp6qRs8tUv0wXy2zAb4cDe6fGh8i=The client dey encrypt item names, usernames, passwords, and notes before e send dem, so the server only dey store ciphertext wey e no fit read. The 2. prefix na Bitwarden encryption type, e follow with initialization vector (IV), the ciphertext, and a MAC (message authentication code), wey all dey in base64 and dem separate am with |. The key wey dey decrypt am come from the account master password, wey no dey ever reach the server in any form wey person fit use. This part dey exactly the same whether you dey run Vaultwarden or the official server, as the comparison of Vaultwarden and self-hosted Bitwarden explain.
The rest of the database no dey encrypted. Email addresses, account names, password hints, and two-factor recovery codes dey stored as plain text, join with metadata like when dem create the item and which organization own am. So, the backup file itself na secret. Anybody wey get am go know who your users be, and dem fit attack the encrypted blobs offline with any speed wey dia hardware fit carry. That single fact na why we get storage rules for down: you must encrypt the copy before e comot for server. The admin token na the other half of the same wahala, and the hardening pass for a self-hosted Vaultwarden cover both of dem.
Why copying db.sqlite3 while Vaultwarden runs is not a backup
Vaultwarden dey run SQLite for WAL mode by default (ENABLE_DB_WAL=true). Write operation dey land for db.sqlite3-wal first, and na only checkpoint dey fold am enter db.sqlite3. If you copy db.sqlite3 alone, you go get the database as e be for the last checkpoint. This one mean say password wey you save ten minutes ago fit miss from your archive without any warning.
To copy all the three files with cp no be solution too. The copies dey happen for slightly different time, so the WAL wey you save fit describe page versions wey no match the main file wey you save again. SQLite go come try recover one from the other and the result go come wrong. You go only know say e spoil when e don too late:
Error: database disk image is malformed.backup dey avoid this wahala because e dey use SQLite Online Backup API. SQLite document this API as the correct way to copy database wey dey active use. E dey read the pages under a read lock, and e dey start fresh if writer change the file while e dey copy. So, wetin land for disk na one consistent moment.
Copy database wit 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;"Di last command go print ok for im own line. Any oda tin wey show mean say di copy no good, so no keep am and no delete di one wey you get before. Di whole process dey run against live server, so nobody go log out and no container go restart.
Di sqlite3 tool no dey inside di Vaultwarden container. Dem build di image on debian:trixie-slim wit ca-certificates, curl, libmariadb3, libpq5 and openssl, so docker exec vaultwarden sqlite3 ... go fail wit dis error:
exec: "sqlite3": executable file not found in $PATHRun am for di host against di mounted path instead, na wetin di commands wey dey up so dey do. If di data dey inside named volume, docker volume inspect <name> go print di host path under /var/lib/docker/volumes/.
Vaultwarden don get im own backup command since version 1.32.1. For your server:
docker exec -it vaultwarden /vaultwarden backupE dey run VACUUM INTO and e dey write db_YYYYMMDD_HHMMSS.sqlite3 inside di data folder. Two tins go happen. Di copy go land near di original one for di same disk, so na staging step e be, no be full backup yet. And na SQLite e dey work for: if you dey use MariaDB or PostgreSQL, e go stop wit The database type is not SQLite. Backups only works for SQLite databases.
Di files wey pipo dey forget
attachments/ dey keep ciphertext wit name wey no clear. Di database row for everi attachment get di encrypted file name and di key material wey client need to decrypt di file. If you get attachment but no get database, e go be like noise wey no get meaning, and if you get database but no get attachment, di user go see item wey no fit download. Make sure you carry both for di same time.
config.json dey keep everi tin wey you save from di admin page, and di values inside get priority pass di environment variables wey match am. Dis one get two side: if you restore old config.json, e go silently override di settings wey dey your compose file, and di file sef dey sensitive sake of say e fit get your SMTP password and your admin token. Store dat token as Argon2id PHC (password hashing competition) string instead of plain text. docker run --rm -it vaultwarden/server /vaultwarden hash fit print one for you.
rsa_key.pem dey sign di JSON web tokens (JWT) wey dey keep clients logged in. If di file no dey wen you start, Vaultwarden go generate new key, so everi token wey di old one sign go stop to dey valid and all clients go log out. Vault contents go still dey because dem dey encrypted wit keys wey dem derive from di master password. If you restore di key file, you go avoid say everibodi go log out for once.
sends/ dey keep di files wey dey behind Send links. If dem miss, dose downloads go break, but noting else go spoil.
Put everytin inside one 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"Save am as /usr/local/sbin/vw-backup.sh, run chmod 700 for am, and run am as root. The test line na im dey do the main work: sqlite3 dey exit 0 even if PRAGMA integrity_check talk say file don corrupt, so na why we dey compare the output with ok to know if the copy fail. set -euo pipefail go come stop everytin, instead of make tar build archive wey no get head with broken database.
The final tar -tzf go list wetin you actually save. Check am well the first time. You dey look for ./db.sqlite3, ./rsa_key.pem, ./config.json and ./attachments/, and make sure say ./db.sqlite3-wal no dey inside. Run am every night with systemd service and timer instead of cron if you want journalctl output and one unit wey go report if anytin fail.
Verify the backup by restoring am go one scratch directory
Backup wey you never test na just guess. To restore am go one scratch directory no go take pass one minute and e no go touch anything wey dey live.
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/attachmentsFour things dey important. integrity_check go print ok. The number of users must match the number of accounts wey you know. The cipher count suppose dey close to the figure wey you see for sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select count(*) from ciphers;", and e no fit ever be zero if people dey use the vault. The attachments directory size suppose dey roughly wetin you expect; you fit skip this one if nobody dey upload attachments. After that, run sudo rm -rf /tmp/vw-check, because that directory now get second copy of everything.
One rule dey when you restore any data folder wey you copy by hand: delete db.sqlite3-wal and db.sqlite3-shm before you start the server. If you no do am, SQLite go try recover the restored database by using log wey belong to another copy, and that one go spoil database wey reach ground intact. Archives wey the script above produce no dey ever contain those files, because .backup dey write one complete database.
Restore go inside the server
Dis one dey run for your own server, wey you don stop the container. Vaultwarden no suppose dey write anytin while you dey change the data folder underneath am.
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 vaultwardenThe chown must show the user wey the container dey run as. The normal image dey run as root, so root:root correct, except you set user: for your compose file; if you do dat, use dat uid and gid. If the server no fit write inside the data folder, the login page go fail every request, and the logs go show am.
One healthy start go end wit the Rocket line:
[INFO] Rocket has launched from http://0.0.0.0:80After dat, login from your browser, open one item, and download one attachment. If login work but attachment download fail, e mean say the archive carry the database but e no carry attachments/. Keep data.old.* until all dose tin work, before you delete am. To roll back, na the same three steps, just swap the directories.
If your paths no match the ones wey dey here, the Vaultwarden install guide for a VPS go show you the compose file wey dis commands dey use.
Where you no suppose keep your backup
- No keep am for the same disk wey your data folder dey. If one volume spoil, e go carry both copies go, and the same thing fit happen if you run one
rm -rffor wrong path. - No keep am for the same server, even if na different volume. If attacker reach root, e go fit access your backups for the same session.
- No keep am for object storage if you no encrypt am, because the archive get email address, password hint, recovery code, and vault ciphertext wey dem fit attack offline.
- No rely only on your provider snapshots. Dem dey restore fast, wey good, but dem dey the same account as your server, so if account get problem, everything go loss.
Offsite copy na where restic dey shine, because restic repository dey encrypted for your machine before e upload anything. For your 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 --prunePoint restic go the archive directory, no be the live data folder, so wetin e upload na the consistent copy wey you don check finish. Keep the repository password for another place wey no be the server wey you dey protect: if you loss that password, you no go fit read the snapshots again, na so dem design am. Where the storage allow am, give the server credentials wey fit write but no fit delete, so if person breach the box, e no go fit wipe the history. Setting up restic backups on a VPS explain the repository and schedule well-well, and restic compared with BorgBackup go help you decide if you never choose.
Test the restore on a schedule
Choose one day every month. Pull the newest snapshot go one scratch directory with restic restore latest --tag vaultwarden --target /tmp/vw-check, run the same PRAGMA integrity_check, run the same row counts, then write the date and the counts down. If nobody don restore backup for six months, you no fit know whether that backup dey work or e don spoil. If you wait until system crash before you check, na that time go be the worst time to find out say e no dey work.
Once every year, do the full version. Start a second Vaultwarden container on a spare port with the restored data folder, and log in with a real account. This one go prove say the master password path dey work from start to finish, wey row count no fit show. restic check --read-data-subset=10% on the same schedule go verify say the data wey you store dey readable, no be just say the file name dey show for list.
FAQ
I fit copy db.sqlite3 with cp while Vaultwarden dey run?
No. Vaultwarden dey run SQLite for WAL mode, so recent writes dey stay inside db.sqlite3-wal and no dey inside db.sqlite3 yet. If you just use cp for the main file, you go lose dem without any warning, and if you copy the two files separately, you fit end up with pair wey no match, wey go later show as Error: database disk image is malformed. Use sqlite3 /path/db.sqlite3 ".backup '/path/out.sqlite3'" instead. E dey use SQLite Online Backup API and e dey produce one consistent file while the server still dey work.
I must stop the Vaultwarden container before I take backup?
No, and na that one be the main reason for .backup. The database copy dey safe even when the server dey run. Dem dey write attachments and Send files when user upload dem, so if file enter between the time wey you copy database and the tar, that file fit miss for that night archive, but na just one attachment you fit lose at worst. If small downtime no worry you, run docker compose stop before the script and docker compose start after am, e go remove even that small risk.
Wetin go happen if I restore without the rsa_key files?
Vaultwarden go generate new key when e start. That key dey sign the JSON web tokens (JWT) wey dey keep sessions active, so every existing token go stop to work and all clients go log out and must sign in again. The vault contents no go spoil, because dem dey encrypted with keys wey come from each user master password, no be with the RSA key. Restore rsa_key.pem join the rest of the data folder and nobody go notice say you restore anything.
The backup archive safe to upload to object storage like that?
No. Item names, passwords, and notes na ciphertext, but email addresses, account names, password hints, and two-factor recovery codes dey plain text inside the database, and person wey get the file offline fit try crack the ciphertext as e like. Encrypt the archive before e commot for the machine. Restic repository go do that one for you, and gpg --symmetric --cipher-algo AES256 vw-20260805-030000.tar.gz go produce one single encrypted file wey you fit give any storage.
How I go back up Vaultwarden if I dey use PostgreSQL or MariaDB?
The SQLite steps no concern you, and the built-in command go refuse with The database type is not SQLite. Backups only works for SQLite databases. Dump the database with the tool wey come with am, pg_dump or mysqldump, and follow all other rules. The dump must dey inside one archive with attachments/, sends/, config.json and the rsa_key files, make you take dem for the same run, encrypt am, and store am for somewhere wey no be the same server wey create am.