VPS Snaps

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.

10 min readUpdated Checked against official documentation

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:

Terminal
docker inspect --format '{{json .Mounts}}' vaultwarden

Settings 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

PathBack up?What it is
db.sqlite3Yes, with .backupAccounts, organizations, devices and every vault item. Items arrive already encrypted by the clients.
db.sqlite3-wal, db.sqlite3-shmNo, when you use .backupThe write-ahead log and its index. .backup already includes the log's committed changes.
attachments/YesFile attachments, one folder per vault item. They are not in the database.
sends/OptionalFiles attached to Sends. Sends are meant to expire; back them up if existing Sends must keep working after a restore.
config.jsonYes, encryptedSettings 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.pemYes, encryptedSigns login tokens. Older installs also have rsa_key.der and rsa_key.pub.der; keep all rsa_key files.
icon_cache/NoWebsite icons, fetched again when needed.
db_*.sqlite3Move them offCopies made by Vaultwarden's built-in backup command.
tmp/NoTemporary 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.

Terminal
apt install sqlite3
Terminal
install -d -m 700 /var/backups/vaultwarden
Terminal
sqlite3 /vw-data/db.sqlite3 ".timeout 10000" ".backup '/var/backups/vaultwarden/db.sqlite3'"
  • .backup copies the database through SQLite's own locking, including changes still in the WAL. Vaultwarden keeps running.
  • .timeout 10000 waits up to 10 seconds for a lock instead of failing at once with database is locked.
  • "VACUUM INTO '/var/backups/vaultwarden/db.sqlite3'" in place of .backup also works and writes a compacted copy. It refuses to overwrite an existing file.

Check the copy. A healthy database prints ok:

Terminal
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:

Terminal
docker exec vaultwarden /vaultwarden backup

It 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.json and 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 -it in 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.

Terminal
pg_dump -h localhost -U vaultwarden -d vaultwarden -Fc -f /var/backups/vaultwarden/vaultwarden.dump
Terminal
mysqldump -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.json holds the admin token and SMTP credentials in plain text.
  • rsa_key.pem signs 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/local/bin/vaultwarden-backup.sh
#!/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" -delete
Terminal
chmod 755 /usr/local/bin/vaultwarden-backup.sh
/etc/cron.d/vaultwarden-backup
15 3 * * * root /usr/local/bin/vaultwarden-backup.sh >> /var/log/vaultwarden-backup.log 2>&1
  • umask 077 makes every file the script creates readable by root only; chown --reference gives the database copy the original's owner, which matters if the container runs as a non-root user.
  • The first -C adds 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 the trap deletes the unfinished .partial file 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:

Terminal
docker stop vaultwarden
Terminal
mv /vw-data /vw-data.before-restore
Terminal
install -d -m 700 /vw-data
Terminal
gpg --decrypt vaultwarden-2026-10-03_0315.tar.gz.gpg | tar -xzf - -C /vw-data
Terminal
docker start vaultwarden
Terminal
docker logs --tail 30 vaultwarden

Never 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.

Terminal
install -d -m 700 /srv/vw-restore-test
Terminal
mkdir /srv/vw-restore-test/data
Terminal
gpg --decrypt vaultwarden-2026-10-03_0315.tar.gz.gpg | tar -xzf - -C /srv/vw-restore-test/data

The outer folder stays private whatever modes the archive restores inside it.

Terminal
sqlite3 /srv/vw-restore-test/data/db.sqlite3 "PRAGMA integrity_check;"
Terminal
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:

Terminal
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"; done

Next, 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:

Terminal
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.3
Terminal
curl -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.

Terminal
ssh -N -L 8001:127.0.0.1:8001 root@<server-ip>
  • -L forwards your local port 8001 to 127.0.0.1:8001 on the server; -N opens no remote shell.
  • A restored config.json overrides environment variables. If it sets domain, change it to http://localhost:8001 in 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:

Terminal
docker rm -f vw-restore-test
Terminal
rm -rf /srv/vw-restore-test

Testing restores covers turning this into a monthly routine.

Common errors

ErrorFix
sqlite3 not found inside the containerThe image leaves it out. Run sqlite3 on the host against the mounted file, or use /vaultwarden backup.
skipped: No public key from gpgRoot's keyring doesn't have the backup key. Import the public key.
There is no assurance this key belongs to the named userThe key is imported but not trusted. Set its owner trust, as the encryption guide shows.
Everyone is logged out after a restorersa_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 downloadThe attachments folder is missing or incomplete, or DOMAIN points somewhere else.
Backup failed from /vaultwarden backupThe 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: