Cara Backup dan Restore Vaultwarden di VPS
Gunakan sqlite3 .backup untuk menyalin vault Vaultwarden yang sedang aktif. Simpan attachments, config.json, dan rsa_key, lalu uji restore sebelum terjadi kerusakan.
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 samping database tersebut. Bagian ini sering terlupakan.
Pada instalasi Docker, folder data adalah folder yang Anda mount ke /data. Folder tersebut dapat berupa path pada host atau named volume. Perbedaan antara bind mount dan named volume menentukan lokasi vault Anda yang sebenarnya pada disk. Folder ini berisi:
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, dengan satu direktori untuk setiap item.sends/: file di balik tautan Bitwarden Send.config.json: setiap 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. Anda dapat melewati satu direktori ini karena Vaultwarden akan mengambilnya kembali saat diperlukan.
Apakah database Vaultwarden saya aman? Isi sebenarnya dari file tersebut
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 hasil seperti ini:
2.k9Qw1nQ0y7Yy2Xw==|E1r0J3l5s7d9f1g3h5j7k9==|Lm4nOp6qRs8tUv0wXy2zAb4cDe6fGh8i=Nama item, nama pengguna, kata sandi, dan catatan dienkripsi oleh klien sebelum dikirim. Karena itu, server menyimpan ciphertext yang tidak dapat dibaca. Prefix 2. menunjukkan jenis enkripsi Bitwarden, diikuti oleh initialization vector (IV), ciphertext, dan MAC (message authentication code), masing-masing dalam format 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, seperti 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 siapa pengguna Anda dan menyerang blob terenkripsi secara offline dengan kecepatan yang memungkinkan oleh perangkat kerasnya. Fakta ini menjadi dasar aturan penyimpanan di bagian berikutnya: salinan dienkripsi sebelum meninggalkan server. Admin token merupakan bagian lain dari masalah yang sama, dan panduan hardening untuk Vaultwarden yang di-host sendiri membahas keduanya.
Mengapa menyalin db.sqlite3 saat Vaultwarden berjalan bukan merupakan backup
Vaultwarden menjalankan SQLite dalam mode WAL secara default (ENABLE_DB_WAL=true). Penulisan awalnya masuk ke db.sqlite3-wal, lalu checkpoint menggabungkannya 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 dibuat pada waktu yang sedikit berbeda. Karena itu, WAL yang disimpan dapat berisi versi halaman yang tidak lagi sesuai dengan file utama yang disimpan. SQLite kemudian memulihkan salah satunya berdasarkan file yang lain, dan hasilnya tidak benar. Anda baru mengetahuinya jauh setelahnya:
Error: database disk image is malformed.backup menghindari masalah ini karena menggunakan SQLite Online Backup API, yang menurut dokumentasi SQLite merupakan cara untuk menyalin database yang mungkin sedang digunakan secara aktif. API ini membaca halaman dengan read lock dan memulai ulang jika writer mengubah file saat proses berlangsung. Dengan demikian, data yang ditulis ke 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 baris tersendiri. Hasil lain berarti salinan tidak dapat digunakan. Jangan simpan salinan tersebut dan jangan hapus salinan sebelumnya. Seluruh rangkaian ini dijalankan pada server aktif, sehingga tidak ada pengguna yang logout dan tidak ada container yang di-restart.
Tool sqlite3 tidak terdapat di dalam container Vaultwarden. Image tersebut dibuat berbasis debian:trixie-slim dengan ca-certificates, curl, libmariadb3, libpq5, dan openssl, sehingga docker exec vaultwarden sqlite3 ... gagal dengan pesan berikut:
exec: "sqlite3": executable file not found in $PATHJalankan perintah tersebut pada host terhadap path yang di-mount. Itulah yang dilakukan oleh perintah-perintah di atas. Jika data berada di named volume, docker volume inspect <name> mencetak path pada host di bawah /var/lib/docker/volumes/.
Sejak versi 1.32.1, Vaultwarden juga menyediakan perintah backup sendiri. 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 dibuat di sebelah file asli pada disk yang sama, sehingga langkah ini baru merupakan staging dan belum menjadi backup. Selain itu, perintah ini hanya mendukung SQLite. Pada MariaDB atau PostgreSQL, perintah berhenti dengan The database type is not SQLite. Backups only works for SQLite databases.
Berkas yang sering terlupakan
attachments/ menyimpan ciphertext dengan nama yang tidak bermakna. Baris database untuk setiap lampiran menyimpan nama berkas terenkripsi dan materi kunci yang diperlukan client untuk mendekripsi berkas tersebut. Lampiran tanpa database tidak dapat dibaca, sedangkan database tanpa lampiran membuat pengguna memiliki item yang unduhannya gagal. Cadangkan keduanya dalam proses yang sama.
config.json menyimpan semua pengaturan yang Anda simpan dari halaman admin, dan nilainya memiliki prioritas atas variabel lingkungan yang sesuai. Dampaknya dapat menguntungkan atau merugikan: memulihkan config.json lama dapat secara diam-diam menimpa pengaturan dalam file compose, dan file itu sendiri sensitif karena dapat berisi kata sandi SMTP serta token admin Anda. Simpan token tersebut sebagai string Argon2id PHC (password hashing competition), bukan teks biasa. docker run --rm -it vaultwarden/server /vaultwarden hash mencetak string tersebut untuk Anda.
rsa_key.pem menandatangani JSON Web Token (JWT) yang membuat client tetap login. Jika file ini tidak ada saat startup, Vaultwarden membuat kunci baru. Akibatnya, semua token yang ditandatangani dengan kunci lama tidak lagi valid dan semua client akan logout. Isi Vault tetap aman karena dienkripsi dengan kunci yang diturunkan dari kata sandi master. Memulihkan file kunci mencegah logout massal.
sends/ menyimpan berkas di balik tautan Send. Kehilangan berkas tersebut hanya merusak unduhan itu.
Masukkan seluruh proses 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, jadikan dapat dieksekusi dengan chmod 700, lalu jalankan sebagai root. Baris test menjalankan pekerjaan penting: sqlite3 tetap keluar dengan kode 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, sehingga tar tidak membuat arsip yang tampak rapi dari database yang rusak.
tar -tzf terakhir mencantumkan data yang benar-benar Anda tangkap. Baca hasilnya saat pertama kali menjalankan skrip. Anda harus menemukan ./db.sqlite3, ./rsa_key.pem, ./config.json, dan ./attachments/, serta tidak menemukan ./db.sqlite3-wal. Jalankan skrip ini setiap malam menggunakan service dan timer systemd, bukan cron, jika Anda menginginkan output journalctl dan unit yang melaporkan kegagalan.
Verifikasi backup dengan memulihkannya ke direktori sementara
Backup yang belum diuji masih berupa asumsi. 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 perlu diperhatikan. integrity_check menampilkan ok. Jumlah pengguna harus sesuai dengan jumlah akun yang Anda ketahui. Jumlah cipher harus mendekati nilai aktif yang ditampilkan oleh sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select count(*) from ciphers;", dan tidak boleh nol pada vault yang sedang digunakan. Direktori attachments harus berukuran kurang lebih 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.
Ikuti 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 milik salinan database yang berbeda. 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 pada server Anda sendiri, saat container dihentikan. Vaultwarden tidak boleh menulis data ketika folder data sedang 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 berisi nama pengguna 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. Jika server tidak dapat menulis ke folder data, halaman login akan 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 unduh satu lampiran. Jika login berhasil tetapi pengunduhan lampiran gagal, berarti arsip hanya membawa database dan tidak membawa attachments/. Pertahankan data.old.* sampai semua pemeriksaan tersebut berhasil, lalu hapus. Proses rollback menggunakan tiga langkah yang sama, tetapi dengan direktori yang ditukar arahnya.
Jika path Anda berbeda dari yang digunakan di sini, panduan instalasi Vaultwarden untuk VPS menampilkan file compose yang menjadi asumsi perintah-perintah ini.
Tempat untuk tidak menyimpan backup
- Jangan menyimpannya pada disk yang sama dengan folder data. Satu volume yang gagal akan membuat kedua salinan tidak dapat digunakan. Hal yang sama berlaku untuk satu
rm -rfpada path yang salah. - Jangan menyimpannya pada server yang sama, meskipun pada volume kedua. Penyerang yang berhasil memperoleh akses root dapat mengakses backup Anda dalam sesi yang sama.
- Jangan menyimpannya di object storage tanpa enkripsi, karena arsip tersebut berisi alamat email, petunjuk password, kode pemulihan, dan ciphertext vault yang dapat diserang secara offline.
- Jangan hanya mengandalkan snapshot dari provider Anda. Snapshot dapat dipulihkan dengan cepat dan tetap berguna, tetapi snapshot berada dalam account yang sama dengan server. Masalah pada account akan turut memengaruhinya.
Salinan di lokasi lain adalah tempat restic berperan, karena repository restic dienkripsi pada mesin sebelum 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, agar yang diunggah adalah salinan konsisten yang sudah Anda periksa. Simpan password repository di tempat selain server yang dilindunginya. Jika password tersebut hilang, snapshot tidak dapat dibaca, sesuai desain. Jika storage mendukungnya, berikan kredensial kepada server yang dapat menulis tetapi tidak dapat menghapus, sehingga kompromi pada server tidak dapat menghapus seluruh riwayatnya. Menyiapkan backup restic pada VPS membahas repository dan jadwalnya secara lengkap, sedangkan restic dibandingkan dengan BorgBackup membahas pilihan tersebut jika Anda belum menentukannya.
Uji pemulihan secara berkala
Pilih satu hari setiap bulan. Salin 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 jumlahnya. Backup yang tidak pernah dipulihkan selama enam bulan memiliki status yang tidak diketahui. Statusnya baru diketahui 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 kata sandi master secara menyeluruh, 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 sedang berjalan?
Tidak. Vaultwarden menjalankan SQLite dalam mode WAL, sehingga penulisan terbaru berada di db.sqlite3-wal dan belum masuk ke db.sqlite3. cp yang hanya mencakup file utama akan menghilangkan penulisan tersebut tanpa pesan, sedangkan penyalinan kedua file secara terpisah dapat menghasilkan pasangan file yang tidak cocok dan baru kemudian menimbulkan Error: database disk image is malformed. 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. File lampiran dan Send ditulis saat pengguna mengunggahnya, sehingga file yang ditambahkan di antara penyalinan database dan tar dapat tidak masuk ke arsip malam itu. Dalam kondisi terburuk, Anda hanya kehilangan satu lampiran. Jika downtime selama beberapa detik tidak menjadi masalah, docker compose stop sebelum script 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 kembali. Isi vault tidak terpengaruh karena dienkripsi menggunakan key yang diturunkan dari master password setiap pengguna, bukan menggunakan RSA key. Pulihkan rsa_key.pem bersama seluruh data folder lainnya agar pemulihan tidak disadari pengguna.
Apakah arsip backup aman diunggah ke object storage tanpa perubahan?
Tidak. Nama item, password, dan catatan berupa ciphertext, tetapi alamat email, nama akun, petunjuk password, dan kode pemulihan two-factor tersimpan sebagai plain text di database. Penyerang offline juga dapat mencoba memecahkan ciphertext dengan kecepatannya sendiri. Enkripsi arsip sebelum meninggalkan mesin. Repository restic melakukannya untuk Anda, sedangkan gpg --symmetric --cipher-algo AES256 vw-20260805-030000.tar.gz menghasilkan satu file terenkripsi yang dapat Anda serahkan ke storage mana pun.
Bagaimana cara membuat backup Vaultwarden pada PostgreSQL atau MariaDB?
Langkah-langkah 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. Ambil semuanya dalam proses yang sama, enkripsi, lalu simpan di lokasi selain server yang membuatnya.