How to back up and restore Vaultwarden
A Vaultwarden backup is a consistent copy of db.sqlite3, made with sqlite3 .backup or Vaultwarden's own backup command, plus the attachments folder, config.json and the rsa_key files from the data folder; sends is optional and icon_cache can be skipped. Encrypt the archive, because config.json holds your admin token and SMTP password in plain text. To restore, stop Vaultwarden, unpack into an empty data folder and start it again.
What Vaultwarden is and where its data lives
Vaultwarden is an unofficial server for the Bitwarden apps and browser extensions, written in Rust. It was called bitwarden_rs until version 1.21.0 in April 2021, and it is not associated with Bitwarden, Inc. Bitwarden's own self-hosted server is a different program with its own backup steps; this guide covers Vaultwarden only.
Vaultwarden keeps everything in one data folder: data next to the binary, or /data inside the official Docker image, mounted from the host. This guide uses /vw-data, the host path in the project's README, and a container named vaultwarden. To find your path, list the container's mounts:
docker inspect --format '{{json .Mounts}}' vaultwardenSettings can move parts of the folder. DATA_FOLDER moves all of it; DATABASE_URL, ATTACHMENTS_FOLDER, SENDS_FOLDER, ICON_CACHE_FOLDER and RSA_KEY_FILENAME move one piece each. If you set any of them, back up those paths too.
What to back up
| Path | Back up? | What it is |
|---|---|---|
db.sqlite3 | Yes, with .backup | Accounts, organizations, devices and every vault item. Items arrive already encrypted by the clients. |
db.sqlite3-wal, db.sqlite3-shm | No, when you use .backup | The write-ahead log and its index. .backup already includes the log's committed changes. |
attachments/ | Yes | File attachments, one folder per vault item. They are not in the database. |
sends/ | Optional | Files attached to Sends. Sends are meant to expire; back them up if existing Sends must keep working after a restore. |
config.json | Yes, encrypted | Settings saved from the admin page, including the admin token and SMTP credentials in plain text. It exists only once the admin page has saved settings. |
rsa_key.pem | Yes, encrypted | Signs login tokens. Older installs also have rsa_key.der and rsa_key.pub.der; keep all rsa_key files. |
icon_cache/ | No | Website icons, fetched again when needed. |
db_*.sqlite3 | Move them off | Copies made by Vaultwarden's built-in backup command. |
tmp/ | No | Temporary uploads. |
Keep what lives outside the folder as well: your compose.yaml or docker run command and any .env file, which hold DOMAIN, ADMIN_TOKEN and other settings. The Vaultwarden wiki advises against relying on filesystem or VM snapshots alone: restoring one is more complex than restoring these files, and more can go wrong.
Copy the database safely
Vaultwarden turns on SQLite's write-ahead log at startup (ENABLE_DB_WAL, on by default). New changes go to db.sqlite3-wal first and reach db.sqlite3 at a checkpoint, so copying db.sqlite3 on its own can miss recent changes or catch a write halfway. Use SQLite's backup API instead. The Docker image has no sqlite3 binary, so install it on the host. Run the commands in this guide as root.
apt install sqlite3install -d -m 700 /var/backups/vaultwardensqlite3 /vw-data/db.sqlite3 ".timeout 10000" ".backup '/var/backups/vaultwarden/db.sqlite3'".backupcopies the database through SQLite's own locking, including changes still in the WAL. Vaultwarden keeps running..timeout 10000waits up to 10 seconds for a lock instead of failing at once withdatabase is locked."VACUUM INTO '/var/backups/vaultwarden/db.sqlite3'"in place of.backupalso works and writes a compacted copy. It refuses to overwrite an existing file.
Check the copy. A healthy database prints ok:
sqlite3 /var/backups/vaultwarden/db.sqlite3 "PRAGMA integrity_check;"SQLite backups explains both methods, and why a plain cp of a live database is unsafe, in detail.
Or use the built-in backup command
Since version 1.32.1, Vaultwarden can copy its own database, which saves installing sqlite3. In Docker:
docker exec vaultwarden /vaultwarden backupIt opens the database read-only, runs VACUUM INTO, and writes a file named db_ plus the UTC date and time, such as db_20261003_031500.sqlite3, next to db.sqlite3: on the host, in /vw-data. It prints Backup to '...' was successful. The Backup Database button on the admin page and the USR1 signal run the same code. Know its limits:
- It copies the database only. Attachments,
config.jsonand the keys are not included. - It works only with SQLite.
- Copies pile up in the data folder. Move each one to your backup location and delete it.
- The wiki's example uses
docker exec -it. Leave out-itin cron, where there is no terminal to attach.
MySQL or PostgreSQL backends
If DATABASE_URL starts with mysql:// or postgresql://, the data folder has no SQLite files and the built-in command does not apply. Dump the database with its own tool, then back up the data folder for attachments, sends, config.json and the keys. Take the two close together, so the attachment files match the rows that point to them.
pg_dump -h localhost -U vaultwarden -d vaultwarden -Fc -f /var/backups/vaultwarden/vaultwarden.dumpmysqldump -h localhost -u vaultwarden -p --single-transaction vaultwarden > /var/backups/vaultwarden/vaultwarden.sql-Fc writes pg_dump's compressed custom format. --single-transaction takes a consistent InnoDB snapshot without blocking the server, and -p prompts for the password. For scripts, keep passwords in ~/.pgpass or an option file, as the pg_dump and mysqldump guides show.
Encrypt the backup
The Bitwarden clients encrypt vault items with keys derived from each user's master password before they reach the server, so db.sqlite3 holds no readable passwords. A leaked backup still does damage:
config.jsonholds the admin token and SMTP credentials in plain text.rsa_key.pemsigns login tokens. With it, anyone can forge admin-page sessions when the admin page is on.- Vault items are only as safe as the weakest master password on the server, because whoever holds the file can try passwords against it offline, without limits.
So encrypt the archive before it leaves the server, to a key whose private half lives elsewhere. Encrypting backups shows how to create a GPG key, import only the public key on the server and mark it trusted. Skip the trust step and gpg in a script stops with There is no assurance this key belongs to the named user.
Keep the private key and its passphrase outside the vault you are backing up, for example on paper in a safe place. If they exist only inside Vaultwarden, losing the server also loses the key to its backups.
Automate it with cron
This script makes a checked .backup copy in a private temporary folder, packs it with the rest of the data folder, encrypts the stream to your key and deletes encrypted backups older than 30 days:
#!/usr/bin/env bash
set -euo pipefail
umask 077
DATA="/vw-data"
BACKUP_DIR="/var/backups/vaultwarden"
KEY="<gpg-key-fingerprint>"
KEEP_DAYS=30
OUT="$BACKUP_DIR/vaultwarden-$(date +%Y-%m-%d_%H%M).tar.gz.gpg"
STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"; rm -f "$OUT.partial"' EXIT
mkdir -p "$BACKUP_DIR"
sqlite3 "$DATA/db.sqlite3" ".timeout 10000" ".backup '$STAGE/db.sqlite3'"
result=$(sqlite3 "$STAGE/db.sqlite3" "PRAGMA integrity_check;")
if [ "$result" != "ok" ]; then
echo "integrity_check failed: $result" >&2
exit 1
fi
chown --reference="$DATA/db.sqlite3" "$STAGE/db.sqlite3"
tar -czf - -C "$STAGE" db.sqlite3 \
-C "$DATA" --exclude='./db.sqlite3*' --exclude='./db_*.sqlite3' \
--exclude='./icon_cache' --exclude='./tmp' . \
| gpg --batch --encrypt --recipient "$KEY" --output "$OUT.partial"
mv "$OUT.partial" "$OUT"
find "$BACKUP_DIR" -name 'vaultwarden-*.tar.gz.gpg' -type f -mtime +"$KEEP_DAYS" -deletechmod 755 /usr/local/bin/vaultwarden-backup.sh15 3 * * * root /usr/local/bin/vaultwarden-backup.sh >> /var/log/vaultwarden-backup.log 2>&1umask 077makes every file the script creates readable by root only;chown --referencegives the database copy the original's owner, which matters if the container runs as a non-root user.- The first
-Cadds the fresh database copy. The second adds the rest of the data folder, minus the live database files, earlier built-in copies, the icon cache and temporary uploads. The patterns start with./because that is how tar names files under., so they leave the copy added first alone. - With
pipefail, a failure in tar or gpg fails the whole pipe, and thetrapdeletes the unfinished.partialfile and the plain-text copy.
A backup on the same disk dies with the server. Copy /var/backups/vaultwarden off the machine each night, for example with rclone, and see cron backup schedules for noticing when a job stops running.
Restore
On a server that has the private key, stop Vaultwarden, move the old folder aside and unpack into an empty one:
docker stop vaultwardenmv /vw-data /vw-data.before-restoreinstall -d -m 700 /vw-datagpg --decrypt vaultwarden-2026-10-03_0315.tar.gz.gpg | tar -xzf - -C /vw-datadocker start vaultwardendocker logs --tail 30 vaultwardenNever put a restored db.sqlite3 next to an old db.sqlite3-wal. SQLite would try to recover the database from the stale log and could corrupt it. Unpacking into an empty folder rules that out. If you made the backup by copying files with Vaultwarden stopped, restore db.sqlite3 and its -wal file together, as a pair.
Run the same Vaultwarden version as the backup, or a newer one, which updates the database when it starts. docker exec vaultwarden /vaultwarden --version shows the running version; pin that image tag instead of latest when you rebuild. If rsa_key.pem did not come back, everyone has to log in again and open invitations stop working. If config.json is missing, settings fall back to your environment variables.
Test a restore without putting a second vault online
A restored copy is a working vault server with real accounts. Unpack it into a private folder, check it, start it where only you can reach it, then delete it.
install -d -m 700 /srv/vw-restore-testmkdir /srv/vw-restore-test/datagpg --decrypt vaultwarden-2026-10-03_0315.tar.gz.gpg | tar -xzf - -C /srv/vw-restore-test/dataThe outer folder stays private whatever modes the archive restores inside it.
sqlite3 /srv/vw-restore-test/data/db.sqlite3 "PRAGMA integrity_check;"sqlite3 /srv/vw-restore-test/data/db.sqlite3 "SELECT (SELECT count(*) FROM users), (SELECT count(*) FROM ciphers), (SELECT count(*) FROM attachments);"That prints the number of accounts, vault items and attachments; run the same query on the live database and compare. Then check that every attachment row has its file. No output means none are missing:
cd /srv/vw-restore-test/data && sqlite3 db.sqlite3 "SELECT cipher_uuid || '/' || id FROM attachments;" | while read -r f; do [ -f "attachments/$f" ] || echo "missing: $f"; doneNext, start the copy on a port bound to 127.0.0.1, with the version you run in production, so nothing outside the server can reach it:
docker run -d --name vw-restore-test -v /srv/vw-restore-test/data/:/data/ -e DOMAIN=http://localhost:8001 -p 127.0.0.1:8001:80 vaultwarden/server:1.37.3curl -s http://127.0.0.1:8001/alive/alive answers with the current time and reads the database to do so, so an answer means Vaultwarden started on the restored data. To log in as well, forward the port from your computer and open http://localhost:8001. Browsers treat localhost as a secure context, which the web vault needs.
ssh -N -L 8001:127.0.0.1:8001 root@<server-ip>-Lforwards your local port 8001 to 127.0.0.1:8001 on the server;-Nopens no remote shell.- A restored
config.jsonoverrides environment variables. If it setsdomain, change it tohttp://localhost:8001in the test copy before starting, or attachment links will point at your real server. - With SMTP configured, logging in from a new browser sends the account's owner the usual new-device email.
- Security keys (FIDO2) are tied to the domain they were registered on and won't work on
localhost. Use an account with another second factor. - Look, don't change: treat the copy as read-only.
Then remove the container and the files:
docker rm -f vw-restore-testrm -rf /srv/vw-restore-testTesting restores covers turning this into a monthly routine.
Common errors
| Error | Fix |
|---|---|
sqlite3 not found inside the container | The image leaves it out. Run sqlite3 on the host against the mounted file, or use /vaultwarden backup. |
skipped: No public key from gpg | Root's keyring doesn't have the backup key. Import the public key. |
There is no assurance this key belongs to the named user | The key is imported but not trusted. Set its owner trust, as the encryption guide shows. |
| Everyone is logged out after a restore | rsa_key.pem was not restored, so Vaultwarden made a new one. Restore it, or let users log in again. |
| Attachments are listed but won't download | The attachments folder is missing or incomplete, or DOMAIN points somewhere else. |
Backup failed from /vaultwarden backup | The server uses MySQL or PostgreSQL. Dump it with mysqldump or pg_dump. |
Frequently asked questions
- Which Vaultwarden files do I need to back up?
- db.sqlite3 (copied with .backup or VACUUM INTO), the attachments folder, config.json and the rsa_key files. The sends folder is optional, and icon_cache can be skipped.
- Can I copy db.sqlite3 while Vaultwarden is running?
- Not with cp. Use sqlite3's .backup, VACUUM INTO or /vaultwarden backup, which make a consistent copy while it runs. A plain file copy is only safe with Vaultwarden stopped, and then the -wal file must be copied with it.
- Does /vaultwarden backup include attachments?
- No. It writes a copy of the SQLite database only, as a db_ file stamped with the date and time in the data folder. Back up attachments, config.json and the rsa_key files separately.
- Do I need to encrypt Vaultwarden backups?
- Yes. Vault items are encrypted by the clients, but config.json holds the admin token and SMTP password in plain text, and anyone with the database can guess master passwords offline.
How this was checked
Commands, limits and prices were checked against these official pages, on October 4, 2026:
- Vaultwarden wiki: Backing up your vault
- Vaultwarden wiki: Changing persistent data location
- Vaultwarden wiki: Running without WAL enabled
- Vaultwarden wiki: Configuration overview
- Vaultwarden wiki: Using the PostgreSQL Backend
- Vaultwarden wiki: Using the MariaDB (MySQL) Backend
- Vaultwarden README (1.37.3)
- Vaultwarden 1.21.0 release notes (project renamed from bitwarden_rs)
- Vaultwarden source 1.37.3: src/main.rs (backup command, USR1, --version)
- Vaultwarden source 1.37.3: src/db/mod.rs (backup_sqlite)
- Vaultwarden source 1.37.3: src/config.rs (folder defaults, ENABLE_DB_WAL)
- Vaultwarden source 1.37.3: src/auth.rs (rsa_key.pem)
- Vaultwarden source 1.37.3: attachment model (file paths and URLs)
- Vaultwarden source 1.37.3: src/api/web.rs (/alive)
- Vaultwarden source 1.37.3: src/api/identity.rs (new-device email)
- SQLite: Using the SQLite Online Backup API
- SQLite: VACUUM
- MySQL 8.4 Reference Manual: mysqldump
- PostgreSQL documentation: pg_dump
- Docker documentation: docker container run
- MDN: Secure contexts
- OpenBSD manual: ssh(1)