Vaultwarden backup and restore drill ================================== Date: 2026-11-07 (UTC) Host: devbox, Ubuntu, Docker 27.5.1 Image: vaultwarden/server:1.34.3, server reports 2025.6.0, SQLite backend Data: one account (drill@example.com), 2 folders, 3 attachments, ciphers as noted Method: every figure below is a command output from this run, not an estimate. The account was created through the real registration API; ciphers were created through the authenticated API. Attachment bytes are test strings, not a real vault's files. Data directory inventory at 40 ciphers -------------------------------------- attachments/a0eeeeec-.../067ec60157fd3c099c12 42 attachments/cb2a5c05-.../04497b57e41710e99496 42 attachments/f0ecbada-.../ca50ace6e9006ef32213 42 db.sqlite3 249856 db.sqlite3-shm 32768 db.sqlite3-wal 1256632 rsa_key.pem 1675 counts: users=1 ciphers=40 folders=2 attachments=3 devices=1 integrity: ok journal_mode: wal TEST 1: copy db.sqlite3 alone while the container runs ----------------------------------------------------- command: cp data/db.sqlite3 backup/db.sqlite3 backup bytes: 249856 (live db 249856, live wal 1256632) restored instance started in 210 ms, /alive 200 restored counts: users=0 ciphers=0 folders=0 attachments=0 devices=0 login: no token; the account does not exist in this copy WAL growth measured separately, from a checkpointed state --------------------------------------------------------- start: db=282624 wal=0 10 cipher creates through the API after: db=282624 wal=214272 ciphers now 110 (about 21 KB of WAL per create; the main file did not move) TEST 2: online backup through the SQLite backup API, container running ---------------------------------------------------------------------- python3 -c 'sqlite3.connect(src).backup(dst)' backup seconds: 0.019 backup file bytes: 262144 integrity of backup: ok backup counts: users=1 ciphers=40 folders=2 attachments=3 devices=1 restored instance started in 211 ms login: token issued ciphers via restored API: 40 old pre-backup token against this copy: 401 (this copy ran under a different DOMAIN) attachment download sha256: a4ac57e42aa4bc3f102d1d54b4f87e1b678f65cf26380df4bc51382d40f985d6 live attachment sha256: a4ac57e42aa4bc3f102d1d54b4f87e1b678f65cf26380df4bc51382d40f985d6 TEST 3: cp -a of the whole data directory while 60 writes land -------------------------------------------------------------- hot copy bytes: 1541057 hot copy integrity: ok hot copy counts: users=1 ciphers=40 folders=2 attachments=3 devices=1 live counts after the writes: users=1 ciphers=100 restored instance started in 209 ms login: token issued ciphers via restored API: 40 (a consistent snapshot from before the writes) The copy is consistent but not current; it was taken over the course of the writes. TEST 4 / TEST 7: restore without rsa_key.pem -------------------------------------------- rsa key regenerated: yes (sha256 31cd214f04dd31c89aeeb7480ef8c3cfbd0b9522c52ff3db276d23bf0b3894af) old token against new-key instance: 401 fresh login after restore: works original rsa_key.pem sha256: 63edc67949709305142e1d860506ac60f222076eb501e7a9687d8b7b40882e70 TEST 5: database restored without attachments/ ---------------------------------------------- restored instance started in 211 ms ciphers via restored API: 40 attachment download: HTTP 404 TEST 6: db.sqlite3 + db.sqlite3-wal, no -shm, same DOMAIN, same rsa_key ----------------------------------------------------------------------- integrity: ok counts: users=1 ciphers=100 folders=2 attachments=3 instance started, /alive 200 old token: 200 ciphers via restored API: 100 attachment download: HTTP 200 attachment sha256: a4ac57e42aa4bc3f102d1d54b4f87e1b678f65cf26380df4bc51382d40f985d6 TEST 8: clean docker stop, then copy db.sqlite3 alone ----------------------------------------------------- before stop: db 249856 wal 2529712 shm 32768 docker stop took 283 ms after stop: db 249856 wal 2529712 shm 32768 after-stop counts read with the wal present: users=1 ciphers=100 copy db.sqlite3 only (249856 bytes), restore with rsa_key and attachments restored instance /alive 200 restored counts on disk: users=0 ciphers=0 folders=0 attachments=0 So a clean stop does not checkpoint the WAL for a single-file copy. TEST 9: the backup script in this post, end to end -------------------------------------------------- command: vaultwarden-backup.sh DATA BACKUP_ROOT integrity ok; users=1 ciphers=110 folders=2 attachments=3 devices=1 backup written: vaultwarden-20261107T141034Z.tar.gz (22090 bytes) wall time: 0.058 s restore: tar -xzf into an empty directory, start the container instance started in 540 ms, /alive 200 login: token issued ciphers via restored API: 110 attachment sha256 matches the live file database integrity of the extracted copy: ok Token claim observed -------------------- iss = http://127.0.0.1:8223|login The restored copies that ran under a different DOMAIN rejected the pre-backup token with 401; the copy that ran with the same DOMAIN and the same rsa_key accepted it with 200. What was not tested ------------------- - PostgreSQL or MySQL backends - a real vault's encrypted blobs (these are test strings) - restoring into an older Vaultwarden release - more than 110 items, or attachments larger than 42 bytes - offsite copy, encryption at rest, retention