Cara Backup dan Restore Vaultwarden di VPS
Gunakan sqlite3 .backup untuk menyalin vault Vaultwarden yang sedang aktif. Sertakan attachments, config.json, dan rsa_key, lalu uji restore sebelum backup diperlukan.
Isi yang harus ada dalam backup Vaultwarden
Backup Vaultwarden adalah salinan seluruh folder data. Database di dalamnya harus disalin dengan cara yang benar. Jalankan sqlite3 db.sqlite3 ".backup out.sqlite3", bukan cp, karena menyalin database yang sedang ditulis dapat menghasilkan file yang tidak dapat dibuka. Setelah itu, simpan file-file yang berada di sebelahnya. Bagian ini sering terlupakan.
Pada instalasi Docker, folder data adalah lokasi yang Anda mount ke /data. Lokasi tersebut dapat berupa path pada host atau named volume. Perbedaan antara bind mount dan named volume menentukan lokasi vault Anda yang sebenarnya pada disk. Berikut isi folder tersebut.
db.sqlite3: semua akun, semua item vault, semua folder, dan semua organisasi. Jika file ini hilang, vault juga hilang.db.sqlite3-waldandb.sqlite3-shm: write-ahead log (WAL) dan indeks shared memory-nya. Penulisan terbaru berada di sini sampai SQLite menggabungkannya ke dalam file utama.attachments/: file yang dilampirkan pengguna ke item vault, dalam keadaan terenkripsi dan disimpan dalam satu direktori untuk setiap item.sends/: file di balik link Bitwarden Send.config.json: semua pengaturan yang Anda simpan dari halaman admin.rsa_key.pem, sertarsa_key.derdanrsa_key.pub.derpada instalasi lama: key yang digunakan untuk menandatangani token login.icon_cache/: ikon situs web yang diunduh. Ini adalah satu-satunya direktori yang dapat dilewati karena Vaultwarden akan mengambilnya kembali saat diperlukan.
Apakah database Vaultwarden saya aman? Isi sebenarnya dalam file
Dua perintah dapat menjawabnya, dan Anda dapat menjalankan keduanya sekarang.
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;"Perintah pertama menampilkan alamat email pengguna Anda dalam teks biasa. Perintah kedua menampilkan satu nama item, dengan tampilan seperti ini:
2.k9Qw1nQ0y7Yy2Xw==|E1r0J3l5s7d9f1g3h5j7k9==|Lm4nOp6qRs8tUv0wXy2zAb4cDe6fGh8i=Nama item, nama pengguna, kata sandi, dan catatan dienkripsi oleh client sebelum dikirim. Karena itu, server menyimpan ciphertext yang tidak dapat dibaca. Prefix 2. adalah jenis enkripsi Bitwarden, yang diikuti initialization vector (IV), ciphertext, dan MAC (message authentication code). Semuanya dikodekan dalam base64 dan dipisahkan oleh |. Kunci untuk mendekripsinya diturunkan dari master password akun, yang tidak pernah dikirim ke server dalam bentuk yang dapat digunakan. Bagian ini sama, baik Anda menjalankan Vaultwarden maupun server resmi, sebagaimana dijelaskan dalam perbandingan Vaultwarden dan Bitwarden yang di-host sendiri.
Bagian database lainnya tidak dienkripsi. Alamat email, nama akun, petunjuk kata sandi, dan kode pemulihan two-factor disimpan sebagai teks biasa, bersama metadata seperti waktu pembuatan dan organisasi yang memiliki suatu item. Karena itu, file backup itu sendiri merupakan rahasia. Siapa pun yang memilikinya dapat mengetahui identitas pengguna Anda dan menyerang blob terenkripsi secara offline dengan kecepatan yang diizinkan oleh perangkat kerasnya. Fakta ini menjadi dasar aturan penyimpanan di bagian berikutnya: salinan dienkripsi sebelum meninggalkan server.
Mengapa menyalin db.sqlite3 saat Vaultwarden berjalan bukanlah backup
Vaultwarden menjalankan SQLite dalam mode WAL secara default (ENABLE_DB_WAL=true). Operasi tulis pertama-tama masuk ke db.sqlite3-wal, lalu checkpoint memasukkannya ke db.sqlite3. Jika Anda hanya menyalin db.sqlite3, Anda mendapatkan database pada checkpoint terakhir. Akibatnya, password yang disimpan sepuluh menit lalu dapat tidak ada dalam arsip tanpa peringatan apa pun.
Menyalin ketiga file dengan cp juga bukan solusi. Salinan tersebut dibuat pada waktu yang sedikit berbeda. Akibatnya, WAL yang disimpan dapat berisi versi halaman yang tidak lagi sesuai dengan file utama yang disimpan. SQLite kemudian memulihkan salah satunya dari yang lain, dan hasilnya salah. Anda baru mengetahuinya jauh kemudian:
Error: database disk image is malformed.backup menghindari masalah ini karena menggunakan SQLite Online Backup API. SQLite mendokumentasikannya sebagai metode untuk menyalin database yang mungkin sedang digunakan secara aktif. Metode ini membaca halaman dengan read lock dan memulai ulang jika writer mengubah file selama proses berlangsung. Dengan demikian, data yang tersimpan di disk merepresentasikan satu keadaan yang konsisten.
Ambil salinan database dengan 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;"Perintah terakhir mencetak ok pada barisnya sendiri. Output lain apa pun berarti salinan tersebut tidak dapat digunakan. Jangan menyimpannya dan jangan menghapus salinan sebelumnya. Seluruh rangkaian ini dijalankan pada server yang aktif, sehingga tidak ada pengguna yang logout dan tidak ada container yang di-restart.
Tool sqlite3 tidak berada di dalam container Vaultwarden. Image tersebut dibangun dengan debian:trixie-slim berbasis ca-certificates, curl, libmariadb3, libpq5, dan openssl, sehingga docker exec vaultwarden sqlite3 ... gagal dengan pesan berikut:
exec: "sqlite3": executable file not found in $PATHJalankan tool tersebut pada host terhadap path yang di-mount. Itulah yang dilakukan oleh perintah di atas. Jika data berada dalam named volume, docker volume inspect <name> mencetak path pada host di bawah /var/lib/docker/volumes/.
Vaultwarden juga menyediakan perintah backup sendiri sejak versi 1.32.1. Pada server Anda:
docker exec -it vaultwarden /vaultwarden backupPerintah tersebut menjalankan VACUUM INTO dan menulis db_YYYYMMDD_HHMMSS.sqlite3 ke dalam folder data. Ada dua hal yang perlu diperhatikan. Salinan tersebut berada di samping file asli pada disk yang sama, sehingga ini merupakan tahap staging dan belum menjadi backup. Selain itu, perintah tersebut hanya mendukung SQLite. Pada MariaDB atau PostgreSQL, perintah akan berhenti dengan The database type is not SQLite. Backups only works for SQLite databases.
Berkas yang sering dilupakan
attachments/ menyimpan ciphertext dengan nama yang tidak bermakna. Baris database untuk setiap lampiran menyimpan nama berkas terenkripsi dan materi kunci yang diperlukan klien untuk mendekripsi berkas tersebut. Lampiran tanpa database hanya menjadi data acak yang tidak dapat dibaca, sedangkan database tanpa lampiran menghasilkan item yang unduhannya gagal. Cadangkan keduanya dalam proses yang sama.
config.json menyimpan semua konfigurasi yang Anda simpan dari halaman admin, dan nilainya memiliki prioritas atas variabel lingkungan yang sesuai. Hal ini dapat menimbulkan masalah: memulihkan config.json lama secara diam-diam akan menimpa pengaturan dalam file compose Anda, dan file itu sendiri bersifat sensitif karena dapat berisi password SMTP serta token admin Anda. Simpan token tersebut sebagai string Argon2id PHC (password hashing competition), bukan sebagai teks biasa. docker run --rm -it vaultwarden/server /vaultwarden hash akan mencetak string tersebut untuk Anda.
rsa_key.pem menandatangani JSON web token (JWT) yang membuat klien tetap login. Jika file tersebut tidak ada saat startup, Vaultwarden akan membuat kunci baru. Akibatnya, setiap token yang ditandatangani dengan kunci lama tidak lagi valid dan semua klien akan logout. Isi Vault tetap bertahan karena dienkripsi dengan kunci yang diturunkan dari master password. Memulihkan file kunci akan mencegah logout massal.
sends/ menyimpan berkas di balik tautan Send. Jika berkas tersebut hilang, unduhan itu gagal, tetapi tidak ada dampak lain.
Masukkan semuanya ke dalam satu skrip
#!/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"Simpan sebagai /usr/local/sbin/vw-backup.sh, chmod 700, lalu jalankan sebagai root. Baris test menjalankan pekerjaan penting: sqlite3 menghasilkan kode keluar 0 meskipun PRAGMA integrity_check melaporkan kerusakan. Karena itu, membandingkan output dengan ok akan membuat salinan yang rusak menyebabkan skrip gagal. Setelah itu, set -euo pipefail menghentikan seluruh proses, bukan membiarkan tar membuat arsip yang tampak rapi dari database yang rusak.
tar -tzf terakhir menampilkan data yang benar-benar Anda ambil. Baca output ini saat pertama kali menjalankannya. Cari ./db.sqlite3, ./rsa_key.pem, ./config.json, dan ./attachments/, serta pastikan ./db.sqlite3-wal tidak ada. Jalankan skrip ini setiap malam menggunakan service dan timer systemd, bukan cron, jika Anda menginginkan output journalctl dan unit yang melaporkan kegagalan.
Verifikasi cadangan dengan memulihkannya ke direktori sementara
Cadangan yang belum diuji masih berupa dugaan. Memulihkannya ke direktori sementara hanya memerlukan waktu satu menit dan tidak menyentuh data yang sedang digunakan.
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/attachmentsAda empat hasil yang penting. integrity_check menampilkan ok. Jumlah pengguna harus sama dengan jumlah akun yang Anda ketahui. Jumlah cipher harus mendekati angka aktif dari sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select count(*) from ciphers;", dan tidak boleh nol pada vault yang sedang digunakan. Direktori attachments harus berukuran kira-kira sesuai perkiraan Anda. Pemeriksaan ini dapat dilewati jika tidak ada pengguna yang mengunggah attachments. Setelah itu, jalankan sudo rm -rf /tmp/vw-check karena direktori tersebut sekarang berisi salinan kedua dari semua data.
Terapkan satu aturan saat memulihkan folder data yang disalin secara manual: hapus db.sqlite3-wal dan db.sqlite3-shm sebelum menjalankan server. Jika tidak, SQLite akan mencoba memulihkan database hasil pemulihan menggunakan log yang berasal dari salinan database lain. Hal ini dapat merusak database yang sebelumnya masih utuh. Arsip yang dibuat oleh skrip di atas tidak pernah berisi file tersebut karena .backup menulis satu database lengkap.
Pulihkan ke server
Jalankan perintah berikut di server Anda sendiri, saat container dihentikan. Vaultwarden tidak boleh menulis data saat folder data diubah.
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 harus mencantumkan user yang digunakan container. Image bawaan berjalan sebagai root, sehingga root:root sudah benar kecuali Anda menetapkan user: dalam file compose. Jika demikian, gunakan uid dan gid tersebut. Folder data yang tidak dapat ditulis oleh server akan menyebabkan halaman login gagal pada setiap permintaan. Log akan menunjukkan penyebabnya.
Proses start yang berhasil diakhiri dengan baris Rocket berikut:
[INFO] Rocket has launched from http://0.0.0.0:80Kemudian login melalui browser, buka sebuah item, dan download satu lampiran. Jika login berhasil tetapi download lampiran gagal, berarti arsip tersebut memuat database, tetapi tidak memuat attachments/. Pertahankan data.old.* sampai semua pemeriksaan tersebut berhasil, lalu hapus. Proses rollback menggunakan tiga langkah yang sama, dengan direktori ditukar arahnya.
Jika path Anda berbeda dari yang digunakan di sini, panduan instalasi Vaultwarden untuk VPS memuat file compose yang menjadi asumsi perintah-perintah ini.
Tempat yang tidak boleh digunakan untuk menyimpan backup
- Jangan gunakan disk yang sama dengan folder data. Satu volume yang gagal akan menghilangkan kedua salinan. Hal yang sama berlaku untuk satu
rm -rfpada path yang salah. - Jangan gunakan server yang sama, meskipun pada volume kedua. Penyerang yang berhasil mendapatkan akses ke root dapat mengakses backup dalam sesi yang sama.
- Jangan gunakan object storage tanpa enkripsi, karena arsip tersebut berisi alamat email, petunjuk password, recovery code, dan ciphertext vault yang dapat diserang secara offline.
- Jangan hanya mengandalkan snapshot dari provider. Snapshot dapat dipulihkan dengan cepat dan tetap berguna, tetapi berada dalam account yang sama dengan server. Masalah pada account akan ikut memengaruhinya.
Salinan offsite adalah tempat restic digunakan, karena repository restic dienkripsi di mesin sebelum data apa pun diunggah. Pada server Anda:
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 --pruneArahkan restic ke direktori arsip, bukan ke folder data aktif, sehingga data yang diunggah adalah salinan konsisten yang sudah Anda periksa. Simpan password repository di tempat lain, bukan pada server yang dilindunginya. Jika password tersebut hilang, snapshot tidak dapat dibaca, sesuai desain. Jika storage mendukungnya, berikan kredensial kepada server yang hanya dapat menulis dan tidak dapat menghapus. Dengan demikian, jika server berhasil dibobol, penyerang tidak dapat menghapus seluruh riwayat backup server tersebut. Menyiapkan backup restic pada VPS menjelaskan repository dan jadwal secara lengkap. restic dibandingkan dengan BorgBackup menjelaskan pilihan tersebut jika Anda belum menentukannya.
Uji pemulihan secara berkala
Pilih satu hari setiap bulan. Tarik snapshot terbaru ke direktori sementara dengan restic restore latest --tag vaultwarden --target /tmp/vw-check, jalankan PRAGMA integrity_check yang sama, jalankan penghitungan baris yang sama, lalu catat tanggal dan hasil penghitungannya. Backup yang tidak pernah dipulihkan selama enam bulan memiliki status yang tidak diketahui. Anda baru mengetahui statusnya saat terjadi gangguan, yaitu waktu terburuk untuk mengetahuinya.
Setahun sekali, lakukan pengujian lengkap. Jalankan container Vaultwarden kedua pada port cadangan dengan folder data yang telah dipulihkan, lalu login menggunakan akun nyata. Ini membuktikan alur master password dari awal hingga akhir, yang tidak dapat dibuktikan oleh penghitungan baris. restic check --read-data-subset=10% pada jadwal yang sama memverifikasi bahwa data tersimpan dapat dibaca, bukan sekadar ditampilkan dalam daftar.
FAQ
Dapatkah saya menyalin db.sqlite3 dengan cp saat Vaultwarden berjalan?
Tidak. Vaultwarden menjalankan SQLite dalam mode WAL, sehingga penulisan terbaru berada di db.sqlite3-wal dan belum masuk ke db.sqlite3. cp hanya pada file utama akan menghilangkan penulisan tersebut tanpa pesan error, sedangkan penyalinan kedua file secara terpisah dapat menghasilkan pasangan yang tidak cocok dan baru menimbulkan Error: database disk image is malformed di kemudian hari. Gunakan sqlite3 /path/db.sqlite3 ".backup '/path/out.sqlite3'" sebagai gantinya. Perintah ini menggunakan SQLite Online Backup API dan menghasilkan satu file yang konsisten saat server tetap melayani permintaan.
Apakah saya harus menghentikan container Vaultwarden untuk membuat backup?
Tidak, dan itulah tujuan .backup. Penyalinan database aman dilakukan pada server yang sedang berjalan. Attachments dan file Send ditulis saat pengguna mengunggahnya. Jadi, file yang ditambahkan di antara penyalinan database dan tar dapat tidak masuk ke arsip malam itu. Dalam kondisi terburuk, Anda hanya kehilangan satu attachment. Jika downtime selama beberapa detik tidak menjadi masalah, docker compose stop sebelum skrip dan docker compose start setelahnya akan menghilangkan risiko tersebut.
Apa yang terjadi jika saya memulihkan data tanpa file rsa_key?
Vaultwarden membuat key baru saat startup. Key tersebut menandatangani JSON web token (JWT) yang mempertahankan sesi, sehingga semua token yang masih ada tidak lagi valid. Semua client akan logout dan harus login lagi. Isi vault tidak terpengaruh karena isinya dienkripsi dengan key yang diturunkan dari master password setiap pengguna, bukan dengan RSA key. Pulihkan rsa_key.pem bersama seluruh isi folder data agar tidak ada pengguna yang menyadari proses pemulihan tersebut.
Apakah arsip backup aman diunggah ke object storage tanpa perubahan?
Tidak. Nama item, password, dan catatan berupa ciphertext. Namun, alamat email, nama akun, petunjuk password, dan kode pemulihan two-factor tersimpan sebagai teks biasa di database. Penyerang yang bekerja secara offline juga dapat mencoba memecahkan ciphertext sesuai kecepatannya sendiri. Enkripsi arsip sebelum meninggalkan mesin. Repository restic melakukan hal tersebut untuk Anda, sedangkan gpg --symmetric --cipher-algo AES256 vw-20260805-030000.tar.gz menghasilkan satu file terenkripsi yang dapat Anda simpan pada storage apa pun.
Bagaimana cara mencadangkan Vaultwarden pada PostgreSQL atau MariaDB?
Langkah untuk SQLite tidak berlaku, dan perintah bawaan akan menolak dengan The database type is not SQLite. Backups only works for SQLite databases. Dump database menggunakan tool bawaannya, yaitu pg_dump atau mysqldump, lalu pertahankan semua aturan lainnya. Dump tersebut harus berada dalam satu arsip bersama attachments/, sends/, config.json, dan file rsa_key. Semua data harus diambil dalam proses yang sama, dienkripsi, dan disimpan di lokasi selain server yang membuatnya.