A password vault is the one self-hosted app where "mostly working" is not good enough. Vaultwarden is small, fast on a fraction of a gigabyte, and every serious client refuses to talk to it without a real certificate — which is the part that actually takes the twenty minutes.
What Vaultwarden is, and is not
Vaultwarden is a from-scratch server implementation of the Bitwarden sync API, written in Rust, distributed as a single small Docker image. It is not Bitwarden's own server software (that project is the much heavier bitwarden/server stack) and it is not affiliated with Bitwarden the company — it is a community project that happens to speak the same protocol, so the official Bitwarden apps and browser extensions work against it once you point them at your own server URL. That compatibility is the entire pitch: your phone, laptop and browser extension do not know or care that the server on the other end is Vaultwarden.
Why HTTPS is not optional here
Every other app in this series can limp along on plain HTTP for testing. Vaultwarden cannot, and the reason is not policy, it is the client software. The web vault runs inside your browser, and once that page is loaded over HTTPS, mixed-content rules block it from talking to a plain http:// server — the browser refuses the request before it leaves the page. The mobile app and browser extension add their own validation on top and reject a non-HTTPS, non-localhost server URL outright, regardless of what the browser would allow. Try to add a plain-http:// server URL anywhere but localhost and it gets refused before it ever reaches your container. There is no setting that turns this off, because it is not Vaultwarden's rule to relax.
Practically this means: get a real hostname and a real certificate before you try to log in from a client, not after. A self-signed certificate does not fix it either — the clients want something a normal trust store accepts, so a real domain behind Let's Encrypt (or equivalent) is the actual requirement, not an optional nicety.
Read this before you buy: the NAT IPv4 catch
Cheap VPS plans very often hand you NAT IPv4 — one shared public address with a short list of forwarded ports — rather than an address that is entirely yours. That is fine for outbound traffic and for SSH on a non-standard port, but it changes how you expose HTTPS.
- If 443 is one of your forwarded ports, point your domain at the VPS, run Caddy on 443 as below, and everything in this guide works exactly as written.
- If it is not, you cannot bind the standard HTTPS port, so the honest fix is to run Caddy on whatever port you were given and put that port in the client's server URL —
https://vault.example.com:8443, say. Every Bitwarden client accepts a port in the server URL field; it is one extra field at setup, not a limitation of the protocol. This only works if port 80 is also forwarded to you: Caddy serves the site itself on 8443, but still needs 80 free to complete the HTTP-01 challenge that gets the certificate from Let's Encrypt in the first place. If 80 is not forwarded either, automatic HTTPS on a non-standard port needs a DNS-01 challenge instead — a Caddy build with your DNS provider's plugin, which the stock image does not ship, plus API credentials — so ask about the dedicated IPv4 below if that is on the table. - The third option is a dedicated IPv4, which gives you 443 outright. On our plans that is available on request by e-mail, not as a self-service add-on.
Worth reading first: NAT IPv4 vs a dedicated IP and NAT IPv4, ports and forwarding.
The compose file
Pin the image tag. Vaultwarden ships new releases regularly and some of them change the database schema, so latest on a vault you actually rely on is a bad trade for saving one line of maintenance. Check the current release at the project's GitHub releases page before copying the tag below — this guide will drift out of date, and running whatever is current matters more than this exact number.
services:
vaultwarden:
image: vaultwarden/server:1.32.7
restart: unless-stopped
environment:
- DOMAIN=https://vault.example.com
- SIGNUPS_ALLOWED=false
- ADMIN_TOKEN=${ADMIN_TOKEN}
- WEBSOCKET_ENABLED=true
volumes:
- vw-data:/data
ports:
- "127.0.0.1:8080:80"
volumes:
vw-data:
The port is bound to 127.0.0.1 on purpose: nothing but the reverse proxy on the same host can reach Vaultwarden directly, so a stray scanner hitting the VPS's public address on port 8080 finds nothing.
Generate the admin token before first boot, not after, then hash it rather than storing it raw:
docker run --rm -it vaultwarden/server:1.32.7 /vaultwarden hash
That prompts for a password — paste the output of openssl rand -base64 48 into it — and prints an Argon2 PHC string. Put that string, not the raw random value, into .env, wrapped in single quotes — an Argon2 string is full of $ signs, and Docker Compose would otherwise try to expand them as variables and hand Vaultwarden a mangled token:
printf "ADMIN_TOKEN='%s'\n" 'paste-the-argon2-phc-string-here' > .env
chmod 600 .env
Vaultwarden accepts a plain token too, but the hashed form is the one its own documentation recommends — it is not vulnerable to timing-attack recovery from the /admin login the way a raw comparison is.
Signups, off by default
SIGNUPS_ALLOWED=false matters more here than the equivalent setting does on most self-hosted apps, because a vault is the one place a stranger creating their own account is a genuinely bad outcome, not just clutter. Leave signups closed and create your own accounts through the /admin page instead, using the token above. If you do want a second household member in, invite them from an existing account rather than reopening signups — invitations work with SIGNUPS_ALLOWED=false set.
The /admin page itself is worth locking down further once you are done using it day to day: it accepts the same token forever unless you rotate it, so treat that token like a root password, not like an application setting.
HTTPS with Caddy
vault.example.com {
reverse_proxy 127.0.0.1:8080
}
That is the whole file — Caddy requests and renews the certificate on its own the first time it sees a request for that hostname, provided DNS already points at the machine. Point the A record first and let it settle; point your domain at your service covers that if you have not done it before. If you are on the forwarded-port path from the NAT section above, change the site block to vault.example.com:8443, open that port, and make sure 80 is forwarded too — Caddy serves the site on 8443 but still needs 80 open to fetch and renew the certificate itself.
WebSockets need to pass through cleanly for live sync between devices to work — Caddy's reverse_proxy handles the Upgrade/Connection headers automatically, which is one less thing to get wrong compared with a manually written nginx config.
Sizing: this one is genuinely small
Vaultwarden is a single Rust binary and a SQLite file holding encrypted blobs — there is no PHP runtime, no Node process, no in-memory cache to budget for. 1 GiB of RAM is plenty, including room for Caddy and the OS itself, even with a handful of accounts and a few hundred items each. Disk is dominated by attachments, if you use them, and those are also usually small. This is the rare self-hosted app where the constraint is not sizing at all — it is getting HTTPS right.
Backups: three things, not one
The data volume holds more than a database, and all of it needs to leave the machine together.
- the SQLite database (
db.sqlite3) — accounts, encrypted vault items, organizations; - attachments, stored as files under the same data directory;
- the RSA key pair (
rsa_key.pem,rsa_key.pub.pem) that Vaultwarden generates on first boot and uses to sign authentication tokens — lose it and every existing client session invalidates, forcing a re-login everywhere, though the vault data itself is still readable.
All three live under /data inside the container, so a volume-level copy catches everything in one pass — but stop the container first. db.sqlite3 is a live database while Vaultwarden runs, and a raw copy taken mid-write can land on a torn, inconsistent snapshot; a few seconds of downtime is cheap insurance against a backup that turns out useless the day you need it.
docker compose stop vaultwarden
docker run --rm \
-v vw-data:/data:ro \
-v "$PWD":/backup \
alpine:3.20 tar czf /backup/vaultwarden-$(date +%F).tar.gz -C /data .
docker compose start vaultwarden
Run it on a schedule and copy the result off the VPS entirely — a tarball sitting next to the volume it came from does not survive the disk it is on failing. back up your VPS is our own write-up of what off-machine backup actually means in practice, on any provider.
Updates
docker compose pull
docker compose up -d
Bump the pinned tag deliberately rather than tracking latest, and skim the release notes first — Vaultwarden documents schema migrations and any breaking environment-variable changes per release, and they run automatically against your SQLite file on the next start. Take the backup above immediately before an update on a vault you cannot afford to lose; a failed migration is rare, but a vault is exactly the file you do not want to find that out about the hard way.
On overnight.host
Full disclosure: this is what we sell. If you want the app without the sysadmin, the managed Vaultwarden container comes with its own hostname and certificate — you get the app and a URL, not a root shell.
One-click apps — EUR 4 to EUR 12 a month, hosted in Germany (EU). Eight apps: n8n, Uptime Kuma, Vaultwarden, Gitea, Nextcloud, Ghost, Managed WordPress, Private AI Chat. Each customer gets an isolated Docker network and volume, plus a hostname under apps.overnight.host on a real wildcard certificate. Memory and CPU are capped per plan by the container runtime.
You order in the shop, pay by card (Stripe) or SEPA bank transfer, and your login details are e-mailed to you once the service is set up. Support is e-mail, run by one person, with no guaranteed response time. All prices are final totals under the German small-business rule (§19 UStG); no VAT is added or shown.
Order one-click-vaultwarden → · One-click apps overview
FAQ
Can I use Vaultwarden over plain HTTP for testing?
Only from localhost, because that is the one address every Bitwarden client also treats as trusted. Anything reached over a network — your phone, a different machine, even another device on your own LAN — needs real HTTPS before the clients will accept the server URL: the web vault is blocked by its own browser's mixed-content rules, and the mobile app and extension refuse a non-HTTPS, non-localhost URL by their own validation regardless of what the browser would allow.
Do I need a dedicated IPv4?
No. A forwarded port for 443 works fine, and if you were not given one, running HTTPS on a non-standard port and putting that port in the client's server URL works exactly as well. A dedicated IPv4 is only for when you want the standard port without any of that, and on our plans it is arranged by e-mail rather than self-service.
Is Vaultwarden as secure as Bitwarden's own server?
It implements the same client-side encryption model — your vault is encrypted and decrypted on your device, and the server only ever stores ciphertext — so a compromise of the server does not hand over readable vault contents either way. What differs is who wrote and maintains the server code: Vaultwarden is an independent, community-maintained reimplementation, not Bitwarden's own software.
What happens if I lose the RSA key pair?
Every client gets logged out and has to re-authenticate, because the tokens they were holding were signed with a key that no longer exists. Your vault data is unaffected — it is encrypted with your own master password, not with that key pair — but it is still a bad afternoon, which is why the key pair is one of the three things backed up above, not an afterthought.
Can I turn signups back on later for one more person?
You do not need to. Existing accounts can send invitations while SIGNUPS_ALLOWED stays false, so a new household member gets in without the vault being open to anyone who finds the URL in between.
Written by the person who runs overnight.host: a small, honest hosting company on dedicated bare metal — Linux VPS, game servers, web hosting. Live status at up.overnight.host.
Top comments (0)