Apakah Vaultwarden Aman? Panduan Hardening
Vaultwarden mengenkripsi setiap item di client, sehingga server tidak menyimpan plaintext. Risiko utama justru token admin dan file backup Anda.
Apakah Vaultwarden aman? Jawaban singkat
Vaultwarden aman pada bagian yang paling penting, karena setiap item vault dienkripsi di perangkat Anda sebelum dikirim ke server. Server menyimpan blob yang tidak dapat dibacanya. Seseorang yang menyalin seluruh database tetap memerlukan master password untuk mendapatkan informasi yang berguna dari dalamnya.
Jawaban tersebut mencakup banyak hal, dan bagian yang dapat gagal adalah bagian yang Anda konfigurasi. Panel admin yang dilindungi token yang mudah ditebak. Port container yang dipublikasikan ke seluruh Internet. config.json dalam plaintext. File tar backup yang dibiarkan di direktori home pada server yang sama. Tidak satu pun dari hal tersebut merupakan masalah kriptografi. Semuanya dapat menyebabkan vault self-hosted dikosongkan.
Semua bagian di bawah ini mengasumsikan instalasi sudah berfungsi. Jika Anda belum memilikinya, siapkan terlebih dahulu menggunakan panduan instalasi Vaultwarden untuk VPS, lalu kembali dan ikuti daftar ini dari atas ke bawah secara berurutan.
Yang sebenarnya disimpan server
Vaultwarden menerapkan model data Bitwarden. Nama item vault, nama pengguna, kata sandi, catatan, dan URI dienkripsi dengan kunci yang diturunkan dari kata sandi utama Anda, di client, sebelum permintaan apa pun dikirim. Isi file lampiran dienkripsi dengan cara yang sama. Server menerima data buram dengan UUID (pengidentifikasi unik universal) yang melekat padanya.
Sebagian data bukan ciphertext. Anda harus mengetahui dengan tepat data tersebut:
- Alamat email akun Anda, dalam teks biasa.
- Pengaturan dan salt KDF (fungsi derivasi kunci) Anda, karena client memerlukannya untuk membangun ulang kunci saat login berikutnya.
- Hash di sisi server dari hash kata sandi utama yang dikirim client, yang digunakan untuk mengautentikasi proses login itu sendiri.
- Metadata: keanggotaan organisasi, nama perangkat, waktu login terakhir.
- Secret untuk metode autentikasi dua faktor yang melindungi login Vaultwarden. Secret ini berada di tabel
twofactortanpa enkripsi, karena server harus menghitung kode yang diharapkan untuk dibandingkan dengan kode Anda. Secret ini berbeda dari secret TOTP (kata sandi sekali pakai berbasis waktu) yang Anda simpan di dalam item vault, yang dienkripsi seperti field lainnya.
Folder data berukuran kecil. Pada instalasi Docker, folder tersebut adalah lokasi yang Anda mount ke /data.
sudo ls -l /vw-data/db.sqlite3 menyimpan hampir seluruh state. attachments/ menyimpan file yang diunggah, satu file untuk setiap UUID, dan merupakan satu-satunya kelas data penting yang tidak disimpan dalam tabel database. sends/ menyimpan lampiran Send dan ditujukan untuk penggunaan sementara. icon_cache/ bersifat sementara dan dapat dibuang. rsa_key.pem beserta file pendampingnya digunakan untuk menandatangani JWT (JSON web token) milik pengguna yang login. Karena itu, salinan private key tersebut dapat digunakan untuk memalsukan sesi login vault. config.json hanya ada setelah Anda mengaktifkan halaman admin. Dokumentasi proyek menjelaskannya secara tegas: file ini menyimpan token admin dan kredensial SMTP Anda dalam teks biasa.
Dengan demikian, model ancaman praktisnya adalah akses ke filesystem, bukan kriptografi jaringan. Akses baca ke satu direktori tersebut memberikan alamat email setiap pengguna, secret 2FA login mereka, kunci untuk memalsukan sesi, serta salinan offline seluruh vault yang dapat diserang kapan saja. Setiap langkah di bawah ini bertujuan mencegah orang lain mengakses direktori tersebut.
Perbaiki token admin terlebih dahulu
/admin adalah panel kontrol penuh: daftar pengguna, undangan, penghapusan, dan setiap pengaturan runtime. Panel ini hanya dilindungi oleh satu rahasia bersama. Tidak ada nama pengguna. Tidak ada autentikasi dua faktor per pengguna.
Panduan lama menyarankan Anda membuat ADMIN_TOKEN dengan openssl rand -base64 48. Cara ini berfungsi dan menulis rahasia dalam teks biasa ke dalam config.json serta file compose Anda. Vaultwarden juga menerima string Argon2 PHC (password hashing competition), sehingga nilai yang disimpan berupa hash. Buat hash dengan container yang sedang berjalan:
docker exec -it vaultwarden /vaultwarden hashAtau tanpa menyentuh container yang sedang berjalan sama sekali:
docker run --rm -it vaultwarden/server /vaultwarden hashPerintah tersebut meminta kata sandi dua kali, lalu mencetak satu baris yang diawali dengan $argon2id$. Pada instalasi bare metal, jalankan ./vaultwarden hash. Jika Anda lebih memilih menggunakan CLI argon2 secara langsung, proyek tersebut mendokumentasikan parameter minimum OWASP:
echo -n 'MySecretPassword' | argon2 "$(openssl rand -base64 32)" -e -id -k 19456 -t 2 -p 1Berikut jebakan yang dapat menghabiskan waktu satu jam. String PHC berisi banyak karakter $, sedangkan Docker Compose memperlakukan $ sebagai interpolasi variabel. Jika Anda menempelkannya tanpa escape ke dalam blok environment:, nilai yang diterima container menjadi rusak, sehingga /admin menolak token yang sebenarnya benar. Ada dua bentuk yang aman. Dalam docker-compose.yml, gandakan setiap $:
environment:
ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$UUZxK1FZMkZoRHFQRlVrTXZvS0E3bHpNQW55c2dBN2NORzdsa0Nxd1JhND0$$cUoId+JBUsJutlG4rfDZayExfjq4TCt48aBc9qsc3UIDalam file .env, escape tidak diperlukan, tetapi gunakan tanda kutip tunggal:
ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'Selanjutnya, batasi laju akses ke panel dan persingkat sesinya:
ADMIN_RATELIMIT_SECONDS=300
ADMIN_RATELIMIT_MAX_BURST=3
ADMIN_SESSION_LIFETIME=20Setelah tiga percobaan yang gagal dalam lima menit, panel berhenti merespons klien tersebut. Sesi admin berakhir setelah 20 menit tanpa aktivitas.
Cara yang lebih baik daripada semua langkah ini adalah menonaktifkan halaman tersebut. Sebagian besar instance hanya memerlukannya sekali untuk mengonfigurasi SMTP dan mengundang pengguna pertama, lalu tidak pernah lagi. Untuk menonaktifkannya, jangan tetapkan ADMIN_TOKEN maupun DISABLE_ADMIN_TOKEN, hapus kunci "admin_token" dari config.json, lalu buat ulang container. Menghapus kunci dari file penting karena halaman admin menulis pengaturan ke sana, dan nilai dalam config.json mengesampingkan environment. Menghapus variabel saja membuat halaman tetap terbuka.
Tutup pendaftaran sebelum ada yang menemukan domain tersebut
SIGNUPS_ALLOWED=false
SIGNUPS_VERIFY=true
SHOW_PASSWORD_HINT=falseSIGNUPS_ALLOWED secara default bernilai true. Biarkan pengaturan ini, karena siapa pun yang mengakses domain Anda dapat membuat akun dan datanya disimpan dalam db.sqlite3 yang sama dengan data Anda. Ubah menjadi false, lalu tambahkan pengguna melalui undangan dari halaman admin. Fitur ini memerlukan SMTP yang berfungsi. INVITATIONS_ALLOWED juga secara default bernilai true dan memungkinkan pemilik organisasi mengundang orang lain. Pengaturan ini sesuai jika Anda mempercayai pengguna, dan seharusnya bernilai false pada instance untuk satu pengguna. Jika hanya domain tertentu yang boleh melakukan pendaftaran, SIGNUPS_DOMAINS_WHITELIST=example.com lebih terbatas daripada pendaftaran terbuka, tetapi jauh lebih lemah daripada undangan.
SHOW_PASSWORD_HINT secara default bernilai false dan sebaiknya dibiarkan demikian. Jika diaktifkan, memasukkan alamat email yang valid pada formulir login akan menampilkan petunjuk master password akun tersebut. Hal ini membocorkan petunjuk dan mengonfirmasi bahwa alamat tersebut terdaftar.
Jika instance Anda pernah menjalankan pendaftaran terbuka selama waktu tertentu, buka halaman admin dan periksa daftar pengguna sebelum menganggap bahwa hanya ada satu akun di dalamnya.
Port yang tidak Anda maksudkan untuk dipublikasikan
Docker image mendengarkan pada port 80 di dalam container. Instalasi bare-metal menggunakan ROCKET_PORT=8000 secara default. Perintah run yang terdokumentasi memublikasikannya seperti ini:
--publish 127.0.0.1:8000:80Awalan 127.0.0.1: adalah inti pengaturannya. Jika Anda menulis -p 8000:80 sebagai gantinya, Docker mengikat 0.0.0.0, lalu menulis aturan DNAT (destination network address translation) ke dalam tabel nat. Aturan tersebut dievaluasi sebelum chain filter yang dikelola ufw. Akibatnya, ufw status melaporkan port tersebut sebagai ditolak, tetapi port tetap menjawab koneksi dari Internet. Mekanisme lengkapnya dijelaskan dalam panduan tentang port Docker yang melewati ufw.
Periksa apa yang benar-benar sedang mendengarkan:
sudo ss -tlnp | grep 8000Hasil yang benar adalah satu baris yang terikat pada 127.0.0.1:8000. Baris yang terikat pada 0.0.0.0:8000 berarti vault terekspos secara langsung. Perbaiki pemetaan, lalu buat ulang container, karena pengikatan port ditetapkan saat container dibuat dan docker compose restart tidak akan mengubahnya:
docker compose up -d --force-recreateSatu port lain masih muncul dalam panduan lama: 3012, yaitu port WebSocket terpisah. Dukungan untuk port tersebut dihapus dalam Vaultwarden 1.31.0 karena trafik notifikasi dipindahkan ke port HTTP utama. WEBSOCKET_ENABLED dan WEBSOCKET_PORT telah diabaikan sejak 1.29.0. Pengaturan yang digunakan saat ini adalah ENABLE_WEBSOCKET, dengan nilai default true. Jika firewall atau file compose Anda masih membuka 3012, tutup port tersebut.
Terminasi TLS pada reverse proxy, bukan di Rocket
Vaultwarden dapat melayani TLS (transport layer security) sendiri melalui Rocket, yaitu framework web-nya, tetapi proyek tersebut menyarankan agar cara ini tidak digunakan di lingkungan produksi. TLS bawaan Rocket tidak mendukung SNI (server name indication) secara ketat. Karena itu, panduan hardening juga menyarankan agar Anda mengakses instance menggunakan hostname dan tidak pernah menggunakan alamat IP langsung. Rentang IP publik dipindai terus-menerus. Vault yang merespons pada alamat IP akan ditemukan.
Bagian server block nginx yang penting:
client_max_body_size 525M;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}Secara default, nginx menetapkan client_max_body_size ke 1 MB. Tanpa baris tersebut, unggahan lampiran gagal dengan 413 Request Entity Too Large di error log nginx, sedangkan Vaultwarden sama sekali tidak mencatat apa pun. Header Upgrade dan Connection membawa handshake WebSocket ke /notifications/hub. Jika header tersebut dihilangkan, vault tetap berfungsi, tetapi perubahan tidak lagi muncul pada perangkat lain sampai Anda memuat ulang halaman secara manual.
Konfigurasi Caddy lebih singkat dan memperoleh sertifikat secara otomatis:
vw.example.com {
reverse_proxy 127.0.0.1:8000 {
header_up X-Real-IP {remote_host}
}
}Selanjutnya, beri tahu Vaultwarden tentang konfigurasi tersebut:
DOMAIN=https://vw.example.com
IP_HEADER=X-Real-IPIP_HEADER secara default sudah bernilai X-Real-IP. Jadi, pastikan proxy benar-benar menetapkan header tersebut. Jika tidak, setiap baris log dan setiap pembatasan laju login akan melihat 127.0.0.1, yaitu proxy itu sendiri. Akibatnya, kegagalan dari satu penyerang dihitung terhadap semua pengguna pada instance tersebut. Tetapkan DOMAIN ke URL https yang sebenarnya. Vaultwarden menggunakan nilai tersebut untuk membuat tautan undangan dan pengaturan ulang kata sandi. Kunci keamanan WebAuthn juga terikat pada origin tersebut.
Ada satu detail yang sering terlewat: koneksi WebSocket mengirim token sesi dalam query string sebagai /notifications/hub?access_token=[JWT]. Token tersebut masuk ke access log proxy dalam bentuk teks biasa. Samarkan parameter access_token dalam format log, atau pastikan log tersebut tidak dikirim ke lokasi mana pun yang tidak Anda kendalikan.
Blokir brute force pada endpoint login
Batas laju aktif secara default (LOGIN_RATELIMIT_SECONDS=60, LOGIN_RATELIMIT_MAX_BURST=10). Batas ini memperlambat penyerang, tetapi tidak menghentikannya. fail2ban dapat menghentikannya, tetapi Vaultwarden harus menulis file log terlebih dahulu. Secara bawaan, Vaultwarden tidak melakukannya:
LOG_FILE=/data/vaultwarden.log
LOG_LEVEL=info
EXTENDED_LOGGING=trueLogin yang gagal kemudian menghasilkan tepat satu baris. Filter Anda harus mencocokkan string ini:
[2026-08-07 09:14:22][vaultwarden::api::identity][ERROR] Username or password is incorrect. Try again. IP: 203.0.113.10. Username: user@example.com.Tulis filter ke /etc/fail2ban/filter.d/vaultwarden.local:
[INCLUDES]
before = common.conf
[Definition]
failregex = ^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =Dan jail ke /etc/fail2ban/jail.d/vaultwarden.local:
[vaultwarden]
enabled = true
port = 80,443
filter = vaultwarden
banaction = %(banaction_allports)s
logpath = /vw-data/vaultwarden.log
maxretry = 3
bantime = 14400
findtime = 14400Jika Anda mempertahankan halaman admin, tambahkan jail kedua dengan failregex bernilai ^.*Invalid admin token\. IP: <ADDR>.*$. Kegagalan login admin dicatat dengan pesan yang berbeda, sehingga filter login tidak akan pernah menemukannya. Kemudian periksa konfigurasi Anda:
sudo systemctl restart fail2ban
sudo fail2ban-client status vaultwardenJail yang berfungsi mencantumkan file log Anda di bawah File list dan melaporkan Currently failed: 0. Masukkan kata sandi yang salah tiga kali dari jaringan lain. Penghitung tersebut akan bertambah, lalu alamat itu muncul di bawah Banned IP list. Jika penghitung tidak pernah bertambah, penyebab yang umum adalah logpath. Nilai tersebut harus berupa path file pada host, bukan path /data/... di dalam container. Penyebab umum kedua adalah X-Real-IP yang tidak ada. Akibatnya, setiap pemblokiran menargetkan proxy Anda sendiri. Konfigurasi lainnya, termasuk jail SSH yang seharusnya sudah Anda jalankan, tersedia dalam panduan fail2ban untuk Ubuntu 24.04.
Kata sandi utama tetap menjadi inti seluruh sistem
Pada enkripsi sisi klien, kata sandi utama adalah kuncinya. Kata sandi utama yang pendek pada instance yang databasenya telah disalin penyerang tidak dilindungi oleh apa pun dalam artikel ini, karena penyerang dapat menyerang salinan tersebut secara offline dengan kecepatan yang diizinkan oleh perangkat keras mereka. Tidak ada pengaturan server yang dapat membatasi serangan pada mesin milik penyerang.
PASSWORD_ITERATIONS=600000 adalah jumlah iterasi KDF yang diberikan kepada klien saat mereka membuat akun baru. Akun yang sudah ada tetap menggunakan nilai saat dibuat, sehingga menaikkannya tidak mengubah apa pun bagi pengguna yang mendaftar tahun lalu. Mereka harus mengubahnya sendiri melalui pengaturan keamanan web vault, yang akan mengenkripsi ulang kunci mereka. Beri tahu mereka, karena tidak ada informasi di antarmuka yang akan menjelaskannya.
Selanjutnya, aktifkan autentikasi dua faktor untuk setiap akun. Fitur ini tidak melindungi ciphertext karena kunci vault hanya berasal dari kata sandi utama. Namun, fitur ini mencegah kata sandi yang dicuri digunakan sendirian untuk login dan menyinkronkan salinan. REQUIRE_DEVICE_EMAIL=true menambahkan langkah konfirmasi email saat akun pertama kali login dari perangkat yang tidak dikenali.
Backup adalah titik kegagalan umum pada vault yang di-host sendiri
Sebuah tar czf folder data yang dibiarkan di direktori home pada VPS yang sama akan membatalkan semua langkah di atas. Arsip tersebut menyimpan db.sqlite3 yang berisi ciphertext setiap pengguna, rsa_key.pem yang dapat memalsukan sesi login, serta config.json yang berisi token admin dan password SMTP dalam teks biasa. Akses baca ke satu file itu berarti akses baca ke vault.
Dua aturan sudah mencakup hal ini. Pindahkan arsip ke luar server. Enkripsi arsip sebelum dikirim.
Ada juga masalah konsistensi data. Menyalin db.sqlite3 menggunakan cp saat service sedang berjalan dapat menghasilkan file yang masih dalam proses penulisan dan tidak dapat dibuka. Anda mungkin baru mengetahuinya saat melakukan restore. Gunakan snapshot bawaan SQLite:
sqlite3 /vw-data/db.sqlite3 ".backup '/tmp/vw-db-backup.sqlite3'"Proses restore, yang sering tidak pernah diuji, dibahas dalam panduan backup dan restore Vaultwarden.
Hal yang Anda lepaskan dibandingkan Bitwarden hosted
Penilaian yang jujur. Layanan hosted Bitwarden dioperasikan oleh orang-orang yang pekerjaan penuhnya adalah mengelola layanan tersebut, dengan audit pihak ketiga yang dipublikasikan dan petugas yang siaga pada pukul 3 pagi. Dengan self-hosting, tanggung jawab itu beralih menjadi kewajiban Anda untuk menerapkan patch secara berkala.
Vaultwarden merilis perbaikan keamanan sebagai rilis biasa. Versi 1.37.0, yang dirilis pada 24 July 2026, merupakan versi saat ini per August 2026, dan catatan rilisnya meminta pengguna melakukan update sesegera mungkin. Instance yang Anda siapkan setahun lalu lalu lupakan akan menjalankan kode yang sudah berusia satu tahun. Tag latest tidak membantu dengan sendirinya: container yang sedang berjalan tetap menggunakan image yang dipakainya saat mulai berjalan sampai Anda menjalankan docker compose pull dan membuatnya kembali. Terapkan unattended upgrades pada Ubuntu untuk paket host, lalu jadwalkan update container sebagai pengingat kalender yang benar-benar akan Anda baca.
Kesimpulan yang seharusnya diambil pembaca secara jujur: kriptografi di sini merupakan rancangan Bitwarden dan tetap kuat, sedangkan risiko operasional sepenuhnya beralih kepada Anda. Jika Anda menerapkan patch dan mencadangkan data ke lokasi lain, instance Vaultwarden pada VPS yang Anda kendalikan merupakan tempat yang wajar untuk menyimpan kata sandi. Jika kedua kebiasaan tersebut tidak akan dilakukan, bayar layanan hosted dan alihkan perhatian Anda ke hal lain. Perbandingan fitur demi fitur tersedia di Vaultwarden dibandingkan dengan Bitwarden yang di-host sendiri.
Perkuat host di bawah container
Vaultwarden adalah satu proses pada mesin Linux, dan root pada mesin tersebut tetap dapat membaca /vw-data, apa pun konfigurasi aplikasi. Jalankan container sebagai pengguna tanpa hak istimewa dengan user: "1000:1000" di file compose Anda, lalu sesuaikan kepemilikan folder data. Pasang apa pun yang tidak perlu ditulis oleh container sebagai read-only dengan :ro. Kemudian tutup akses dari luar: Pengerasan SSH pada VPS membahas login hanya dengan key dan penonaktifan autentikasi password. Langkah ini mencegah serangan sederhana yang dapat melewati semua perlindungan sebelumnya.
FAQ
Apakah seseorang dapat membaca kata sandi saya jika mereka mencuri database Vaultwarden?
Tidak secara langsung. Setiap item vault dienkripsi di client menggunakan kunci yang diturunkan dari master password, sehingga db.sqlite3 berisi ciphertext. Data yang segera mereka dapatkan adalah alamat email setiap akun, pengaturan KDF, metadata login dan perangkat, serta secret two-factor pada tabel twofactor. Data tersebut disimpan tanpa enkripsi karena server harus menghitung kode yang diharapkan. Mereka juga dapat menyerang ciphertext vault secara offline selama yang mereka inginkan. Karena itu, panjang master password adalah faktor yang menentukan hasilnya.
Apakah saya harus menggunakan ADMIN_TOKEN atau menonaktifkan halaman admin sepenuhnya?
Nonaktifkan jika memungkinkan. Sebagian besar instance hanya membutuhkannya sekali untuk mengonfigurasi SMTP dan mengundang pengguna. Setelah itu, halaman tersebut biasanya tidak diperlukan lagi. Untuk menonaktifkannya, jangan tetapkan ADMIN_TOKEN maupun DISABLE_ADMIN_TOKEN, hapus kunci "admin_token" dari config.json, lalu buat ulang container. Menghapus variabel environment saja tidak cukup karena pengaturan yang ditulis melalui halaman admin berada di config.json dan memiliki prioritas lebih tinggi. Jika halaman tersebut tetap digunakan, simpan token sebagai hash Argon2 yang dihasilkan oleh vaultwarden hash, bukan sebagai string acak plaintext, lalu tetapkan ADMIN_RATELIMIT_MAX_BURST=3.
ADMIN_TOKEN saya benar, tetapi /admin menolaknya. Apa masalahnya?
Hampir selalu masalahnya adalah interpolasi $. String PHC Argon2 berisi beberapa karakter $. Docker Compose memperluas karakter tersebut sebagai variabel di dalam blok docker-compose.yml environment:, sehingga nilai yang diterima container berubah, meskipun file Anda terlihat benar. Gandakan setiap $ menjadi $$ di file compose, atau pindahkan nilainya ke file .env yang diapit tanda kutip tunggal. Dengan cara ini, escaping tidak diperlukan. Buat ulang container setelahnya karena perubahan environment tidak diterapkan hanya dengan restart.
Apakah saya masih perlu membuka port 3012 untuk notifikasi?
Tidak. Dukungan untuk trafik WebSocket pada port 3012 dihapus di Vaultwarden 1.31.0 karena notifikasi dipindahkan ke port HTTP utama. Selain itu, WEBSOCKET_ENABLED dan WEBSOCKET_PORT telah diabaikan sejak 1.29.0. Pengaturan yang digunakan saat ini adalah ENABLE_WEBSOCKET, yang secara default bernilai true. Tutup port 3012 di firewall dan hapus port tersebut dari file compose. Setelah itu, pastikan reverse proxy meneruskan header Upgrade dan Connection karena sinkronisasi real-time kini bergantung pada header tersebut.