Vaultwarden: Self-Hosting a Password Manager You Actually Control
Bitwarden's vault, on hardware you own

Contents
A password manager is the single most important piece of software most people never think about. It quietly holds the keys to your email, your bank, your tax account, and the embarrassing forum you joined in 2009 and forgot to delete. Handing that responsibility to a cloud service is perfectly reasonable, and the big providers do a genuinely good job. But if you would rather your encrypted vault lived on a box in your own cupboard than on someone else’s servers, Vaultwarden is the project that makes self-hosting practical without demanding you become a cryptographer first. This guide walks through what it is, how to stand one up safely, and the honest trade-offs you accept when you take the keys back.
Why Self-Host a Password Manager at All
The case for self-hosting is partly about control and partly about economics. Hosted Bitwarden is inexpensive, but premium features such as organisations, file attachments, and TOTP storage sit behind a subscription. Vaultwarden unlocks essentially all of those features for the cost of running a small container. That alone tempts plenty of people.
The deeper motivation is custody. Even though a well-designed vault is encrypted client-side, meaning the server only ever sees ciphertext, some people simply prefer that the ciphertext never leaves their control. If you run the server, there is no third party to subpoena, no business model that might change, and no remote outage that locks you out of your own logins. You also get to decide exactly who can create an account, which matters more than it sounds.
None of this is an attack on hosted services. For most households, paid Bitwarden is the sensible choice precisely because someone else handles uptime, patching, and backups. Self-hosting is for people who actively want that work, or who already run a home server and would like one more useful service on it.
What Vaultwarden Is
Vaultwarden is an unofficial, community-built server that speaks the Bitwarden API. It was originally written under the name “bitwarden_rs” and is implemented in Rust, which is why it is so light: it happily runs in a few tens of megabytes of memory on a Raspberry Pi, where the official Bitwarden server stack is comparatively heavy and assumes a more serious deployment.
The clever part is compatibility. Because Vaultwarden implements the same API the official clients expect, you keep using the real Bitwarden apps, browser extensions, and command-line tools. You simply point them at your own server URL instead of the public one. Your vault, your sync, your autofill, all unchanged from the user’s point of view, but the backend is yours.
It is worth being precise about the relationship: Vaultwarden is not produced or endorsed by Bitwarden the company. It is an independent reimplementation. That brings flexibility and a low resource footprint, but it also means you are trusting a separate project’s code and its security track record, which has been good but is not the same organisation.
Installing With Docker Compose
The cleanest way to run Vaultwarden is in a container. The example below assumes you already have Docker and the Compose plugin installed, and that you will be putting the service behind a reverse proxy (more on that shortly, because it is mandatory).
Create a directory and a compose.yaml:
| |
| |
Notice the port binding is 127.0.0.1:8080:80. That deliberately exposes the container only on the loopback interface, so nothing reaches it except your reverse proxy.
Now the admin token. The quick-and-dirty version is a long random string, but current Vaultwarden strongly prefers an Argon2-hashed token so that a leaked environment or config file doesn’t hand over the plaintext. Vaultwarden bundles a helper for exactly this — generate the hash and it prints an $argon2id$… string:
| |
Paste that hash into a .env file beside the compose file. One gotcha that catches everyone: in an env file the $ characters must be escaped by doubling them ($ becomes $$), or Docker Compose will try to expand them as variables:
| |
If you truly just want a random string for a throwaway internal instance, openssl rand -base64 48 still works as the token value — but for anything internet-facing, hash it.
Then bring it up:
| |
The logs will confirm it has started. At this point the service is running but is not yet reachable from your browser over a proper address, which is exactly how it should be until HTTPS is in front of it.
HTTPS Is Not Optional
This deserves its own section in bold letters: never expose Vaultwarden over plain HTTP. The Bitwarden clients use the Web Crypto API, which browsers only permit over a secure context. More fundamentally, your login flow and vault sync traffic must be encrypted in transit. Running a password manager over HTTP is the security equivalent of mailing your house keys on a postcard.
The standard approach is a reverse proxy that terminates TLS and forwards to the loopback port. Caddy is the gentlest option because it obtains and renews certificates automatically. A complete Caddyfile can be as short as this:
| |
Caddy will fetch a certificate from Let’s Encrypt, redirect HTTP to HTTPS, and keep the certificate renewed. If you prefer Nginx or Traefik the principle is identical: terminate TLS at the proxy, proxy to 127.0.0.1:8080, and forward the standard headers. Make sure the DOMAIN environment variable in your compose file matches the public HTTPS URL exactly, because Vaultwarden uses it to construct links and validate requests. If you’re running this inside a Kubernetes cluster rather than plain Docker, the certificate mechanics shift to cert-manager for automated TLS, but the “never plain HTTP” rule is identical.
If your server is only ever reached from inside your own network, you still want TLS, but you can use an internal certificate authority or a wildcard certificate for a domain you control rather than exposing the box to the public internet.
Using the Official Clients Against Your Server
Once HTTPS is live, open https://vault.example.com in a browser. Because signups are disabled, you create the first account through the admin page or by temporarily enabling signups, then turning them off again.
In any official Bitwarden client, the trick is the same. Before logging in, open the settings (often a cog icon on the login screen) and set the Server URL to your domain. Then log in with your vault credentials as normal. This works in:
- The browser extensions for Firefox, Chrome, and the rest
- The desktop applications on Linux, macOS, and Windows
- The mobile apps on Android and iOS
- The
bwcommand-line tool, viabw config server https://vault.example.com
From here, autofill, password generation, TOTP codes, and folders all behave exactly as they would against hosted Bitwarden, because they are the very same apps.
The Admin Page and Locking Down Signups
The admin interface lives at https://vault.example.com/admin and is gated by the ADMIN_TOKEN you generated. From here you can invite users, see diagnostics, and tweak settings without editing environment variables by hand.
The most important setting is open registration. By default you should keep SIGNUPS_ALLOWED set to false, because an internet-facing vault server with open signups is an invitation for strangers to create accounts on your hardware. If you want to add family members, either invite them explicitly from the admin page or flip signups on briefly, register the accounts, and flip them straight back off. For tighter control, the SIGNUPS_DOMAINS_WHITELIST option restricts registration to specific email domains.
Treat the admin token like a root password. Anyone who has it can reconfigure your server.
Backups and Disaster Recovery
Everything that matters lives in the ./data directory you mounted: the SQLite database, attachments, the RSA keys used to sign tokens, and your configuration. Back that directory up and you can rebuild the entire service anywhere.
Because the database is held open while the container runs, a clean backup is best taken with the service briefly stopped, or via SQLite’s online backup. A simple nightly approach:
| |
The stop window is a couple of seconds. Send those archives somewhere off the machine, encrypt them at rest, and actually test a restore once, because an untested backup is just a hopeful rumour. Remember that the vault contents are already encrypted, but the backup archive also contains the signing keys, so guard it carefully.
Troubleshooting the Common Snags
A handful of problems account for most first-time headaches, so here they are with fixes.
“Invalid admin token” even though it’s correct. Nine times out of ten this is the $$ escaping in the .env file. An Argon2 hash is full of $ characters, and if you didn’t double them, Compose silently mangled the value. Print what the container actually received with docker compose exec vaultwarden env | grep ADMIN and compare.
Browser refuses to unlock the vault. The Bitwarden clients need the Web Crypto API, which browsers only expose over a secure context. If you’re hitting the box over plain HTTP or with an untrusted certificate, unlock fails. Get valid TLS in front of it — there is no workaround, and that’s by design.
Login attempts hammering the server. An internet-facing vault will attract credential-stuffing bots. Vaultwarden has built-in rate limiting that will lock out an IP after too many failed attempts, and you can tune it via the LOGIN_RATELIMIT_MAX_BURST and LOGIN_RATELIMIT_SECONDS settings. For belt-and-braces, front the proxy with a tool that bans IPs on repeated 401s at the edge. Restricting the whole thing to a VPN is even better if you don’t need public access.
“WebSocket / live sync isn’t working.” Older reverse-proxy configs forget to forward WebSocket upgrade headers. Caddy handles this automatically; on Nginx you need the Upgrade and Connection headers proxied through, or notifications and instant sync go quiet.
Security Posture and Honest Caveats
The reassuring part of the threat model is that your vault is encrypted on the client before it ever reaches the server, derived from your master password. A server compromise exposes ciphertext, not plaintext passwords, provided your master password is strong. The server still sees metadata, login timing, and the structure of your vault, so it is not a magic invisibility cloak.
Now the caveats, stated plainly. You are the operations team now: patching the container, renewing certificates, monitoring for the day it falls over, and keeping backups current. That labour is real and easy to under-count, which is the whole argument in self-hosting is not free: accounting for your own time — the software costs nothing, your attention does not. If your server dies and your backups are stale, that is on you. Vaultwarden is excellent software but is a community project, not the official server, so you inherit its update cadence. And exposing any service to the public internet enlarges your attack surface, so keep the host updated, consider restricting access to a VPN, and never reuse the admin token anywhere else.
Self-hosting a password manager is one of the more rewarding home-server projects precisely because the stakes make you careful. Done properly, you end up with a fast, private, fully featured vault that answers only to you, which is exactly the point.




