Skip to content

How to Deploy Bitwarden on a VPS

Updated Aug 2026

Self-host the official Bitwarden server on your own VPS — the vendor-supported, on-prem password manager stack, with HTTPS, SMTP, and backups done properly.

Before you start
  • A VPS with 2 vCPU / 4 GB RAM and ~25 GB disk (the official stack is multi-container)
  • A fresh Ubuntu 26.04 server with root/sudo SSH access
  • A domain you can point at the server — HTTPS is mandatory for Bitwarden clients
  • A free installation ID and key from bitwarden.com/host
  • An SMTP sender — invites, email verification, and the admin portal login all go out by email
  • Docker Engine + Compose installed (see the base guide below)

What self-hosted Bitwarden is

Bitwarden is the password manager most people know from its cloud service — and the same company ships the official self-hostable server under the AGPL. Running it yourself means the vaults, the identity service, and the database all live on a box you own, while the browser extensions, desktop apps, and mobile apps stay exactly the ones you already use. You point a client at your own URL instead of the vendor's, and everything else behaves the way it does on the hosted plan.

The distinction that matters before you spend an evening on this: Bitwarden is not Vaultwarden. Vaultwarden is an independent reimplementation of the same API in Rust — one small container, a SQLite file, tens of megabytes of RAM. Bitwarden's own server is a multi-container deployment: the web vault, the API, an identity service, an events service, an admin portal, a bundled nginx, and Microsoft SQL Server underneath it. It wants real memory and real disk, and it takes a couple of minutes to come up.

So why choose it? Three honest reasons. You get the vendor's own code, which means the enterprise features (SSO, directory sync, policies, an admin portal for managing the whole organization) are available under a licence rather than absent. You get support if you pay for it — a real consideration for a company vault. And you get a deployment the vendor tests, documents, and upgrades with a single script. If you're a household or a five-person team who just wants vaults to sync, Vaultwarden is the lighter, cheaper, entirely reasonable pick. If you're an organization that needs the supported product on infrastructure you control, this guide is the one.

Be clear-eyed about what you're taking on. This is your entire credential store, and once it's yours you own its uptime, its certificate, and its backups. HTTPS is non-negotiable, email is effectively required, and an untested backup of a password vault is a bet you don't want to be making on a bad day.

Server sizing — why this one actually needs the RAM

Bitwarden publishes system requirements for the standard Linux deployment: a minimum of an x64 processor around 1.4 GHz, 2 GB RAM, and 12 GB storage, with a recommended spec of a dual-core 2 GHz x64 processor, 4 GB RAM, and 25 GB storage. It also expects a current Docker Engine with the Compose plugin.

Take the recommended numbers seriously. The dominant consumer here is SQL Server, which reserves memory aggressively and is genuinely unhappy on a 1 GB box — this is not a workload where the published floor is a comfortable place to live. On a 2 GB instance you can get the stack running, but a routine update (which pulls and recreates every container) is where it falls over. 4 GB is the number to budget for, and the disk figure matters too because the database, its nightly backups, and the container images all land in the same place.

That makes this a meaningfully more expensive thing to run than Vaultwarden — a few dollars a month becomes a modest-but-real monthly bill. It's still flat, though: no per-seat pricing, no per-vault charge, the same box whether five people use it or fifty. If the RAM figure is what pushes you away, that's a legitimate signal to reconsider Vaultwarden rather than to undersize this.

Reliability trumps price here. A Hetzner instance in the 4 GB tier is the value pick, DigitalOcean is the easy button with snapshots a click away, and Kamatera lets you size CPU and RAM independently if you want more headroom on one axis than the other. All three are covered at the end.

Prepare the server

This guide assumes Docker Engine and the Compose plugin are installed, along with a non-root deploy user, a ufw firewall, and unattended security updates. If that's not done yet, work through Docker & Compose on Ubuntu first — it's the base layer for every app on this site.

Bitwarden's installer is different from most things you'll deploy: it wants to bind ports 80 and 443 on the host itself, because it ships its own nginx and can fetch a Let's Encrypt certificate during installation. So the firewall needs exactly those two ports plus SSH:

sudo ufw status verbose

You should see OpenSSH, 80/tcp, and 443/tcp allowed and everything else denied. If you already run a web server or reverse proxy on this box, stop here and read the HTTPS section below before installing — you have a port conflict to resolve first, and it's much easier to decide now than to unpick later.

Point DNS before you install. Create an A record for your vault hostname (say vault.example.com) pointing at the server's public IP, and wait for it to resolve:

dig +short vault.example.com

The installer asks for that domain, and if you let it request a Let's Encrypt certificate, the ACME challenge validates against this record during the install itself. A name that doesn't resolve yet turns a clean install into a retry.

Get an installation ID and key

This is the step that surprises people, so it's worth its own heading: the official self-hosted server requires a free installation ID and installation key, which you request from bitwarden.com/host with an email address. They're free, they take a minute, and the installer will sit there and wait for them — so fetch them before you start rather than in the middle of a prompt.

They aren't a licence. They identify your instance to Bitwarden for things like push-notification relay and update checks. You'll also be asked to pick a region for that association. Keep both values somewhere safe; they end up in your configuration and you'll want them again if you ever rebuild the box from scratch.

Install Bitwarden

Everything happens through a single script, run as your non-root user in a directory that will hold the whole deployment. Create it, then fetch the installer:

mkdir ~/bitwarden && cd ~/bitwarden
# Official Bitwarden self-host installer
curl -Lso bitwarden.sh "https://func.bitwarden.com/api/dl/?app=self-host&platform=linux"
chmod +x bitwarden.sh
./bitwarden.sh install   # prompts for domain + install id/key

The install is interactive. It asks, in order, for:

  • the domain name for your instance (vault.example.com — no scheme, no path)
  • whether to use Let's Encrypt for SSL, and an email address if you say yes
  • the database name to create
  • your installation ID and installation key
  • a region, and if you declined Let's Encrypt, which certificate route you want

When it finishes, you have a bwdata/ directory next to the script. Two files in there are the ones you'll actually touch:

  • ./bwdata/config.yml — the deployment's structural configuration, including the ports and certificate paths its bundled nginx uses.
  • ./bwdata/env/global.override.env — the runtime environment: SMTP settings, the admin portal allowlist, and the registration switch.

Start the stack:

./bitwarden.sh start

The first start pulls a set of images and initializes the database, so give it a few minutes before you judge it. Watch what comes up with docker ps — you should see the web vault, api, identity, admin, events, nginx, and mssql containers all in a running state. If one is restarting in a loop, jump to Troubleshooting.

HTTPS + domain (mandatory)

The Bitwarden clients refuse to talk to a server that isn't served over HTTPS. That's a hard requirement in the official apps and extensions, not a warning you can click past. A self-hosted instance on plain http:// is, for practical purposes, an instance no real client will connect to. Bitwarden's own documentation is blunt about it: if you choose the no-certificate option, you must front the installation with a proxy that serves it over HTTPS.

You have two sane routes, and the right one depends on whether this box does anything else.

Route A — let the installer handle it (recommended for a dedicated box). Answer yes to the Let's Encrypt prompt during ./bitwarden.sh install. Bitwarden's bundled nginx obtains the certificate, serves 443, and renews on its own; there's a ./bitwarden.sh renewcert subcommand if you ever need to force it. Nothing else to configure, nothing else to run. If this VPS exists to host the vault and nothing else, take this route and skip to First-run setup.

Route B — put Caddy in front (use this if the box serves anything else). Because Bitwarden's own nginx wants 80 and 443, you cannot also run a reverse proxy on those ports without moving Bitwarden's out of the way first. To do that: answer no to the certificate prompt during install, then edit ./bwdata/config.yml and change the HTTP and HTTPS port entries to unused high ports (say 8081 and 8443). Apply the change with:

./bitwarden.sh rebuild
./bitwarden.sh restart

rebuild regenerates the deployment's generated config from config.yml; restart brings the containers back on the new ports. Then follow Automatic HTTPS with Caddy and give Caddy a single block:

vault.example.com {
    reverse_proxy 127.0.0.1:8081
}

Caddy terminates TLS on 443, fetches and renews the certificate itself, and forwards to Bitwarden's nginx on the loopback port. One wiring detail: if you run Caddy as a container rather than on the host, 127.0.0.1 inside that container is the container's own loopback, not the host's — give the Caddy service network_mode: host, or proxy to the host's LAN address instead.

Whichever route you take, the domain you configured must match how clients reach you, https:// and all. A mismatch between the installed domain and the URL people type produces login failures that look like server errors.

Configure email (SMTP)

Treat this as required, not optional. On a self-hosted instance, email carries: user invitations, email-address verification, new-device login alerts, and — the one that catches everybody — the login link for the System Administrator Portal at /admin, which authenticates by emailing a passwordless link. No working SMTP means no admin portal.

Open ./bwdata/env/global.override.env and fill in the mail block plus the admin allowlist:

Open the override file in your editor:

nano ~/bitwarden/bwdata/env/global.override.env
globalSettings__mail__smtp__host=smtp.example.com
globalSettings__mail__smtp__port=587
globalSettings__mail__smtp__ssl=false
globalSettings__mail__smtp__username=apikey-or-user
globalSettings__mail__smtp__password=your-smtp-password
globalSettings__mail__replyToEmail=vault@example.com
adminSettings__admins=you@example.com

Two details worth knowing. globalSettings__mail__smtp__ssl is a boolean where true means implicit SSL and false means STARTTLS — for the common port 587 submission setup, false is what you want. And dollar signs are not supported in the SMTP password value, so if your provider handed you a password containing one, regenerate it rather than fighting the escaping.

adminSettings__admins is a comma-separated list of the email addresses allowed into the admin portal. Keep it to real administrators; anyone on that list can manage users and licensing for the whole instance.

Apply the changes:

./bitwarden.sh restart

Any SMTP relay works — a transactional email provider is the least painful option, since a VPS sending mail directly will mostly land in spam folders.

First-run setup

Open https://vault.example.com in a browser. You should get the Bitwarden web vault with a valid certificate.

Create your own account now. Register through the normal sign-up flow, verify the email (this is your first real test that SMTP works), and log in. Then create an organization if more than one person will use this instance — on the official server, sharing, groups, and collections all hang off an organization rather than off individual accounts, which is a meaningful difference from Vaultwarden's model.

Then close public registration. An internet-facing vault where anyone who finds the URL can create an account is the classic serious mistake. Once your own account exists, add the switch to global.override.env:

globalSettings__disableUserRegistration=true

and restart:

./bitwarden.sh restart

Existing users are unaffected and you can still bring people in by inviting them into your organization — which is the better flow anyway, since it puts them under your policies from the start. Verify by loading the site in a private window: the registration page should refuse to create an account.

Finally, connect a real client. In the browser extension, desktop, or mobile app, open the settings on the login screen (usually a gear icon) and set the self-hosted server URL to https://vault.example.com before logging in. From then on that client syncs through your server.

Hardening

The two big ones are already done — HTTPS in front, registration closed. A few more habits make this genuinely solid:

  • Keep the admin allowlist tight. adminSettings__admins is the portal's entire access control, and the portal can manage every user on the instance. One or two addresses, both of which you control.

  • Turn on two-step login for every account, starting with yours. The self-hosted server supports authenticator apps out of the box; the organization policy that requires two-step login for members is worth enabling as soon as you have more than one user.

  • Don't skip host hardening. SSH keys only, no password authentication, ufw limited to 22/80/443, unattended security updates on. The application is only as safe as the box under it, and this box holds every password your team has.

  • Keep it patched. Bitwarden ships releases regularly, security fixes included. The update path is one command (below) — the failure mode is letting it drift for a year, then discovering the jump is bigger than you wanted on the day you needed it to be small.

Backups

Bitwarden's documentation names four paths inside bwdata as the ones a backup must capture:

  • ./bwdata/env — environment variables, including the database and certificate passwords
  • ./bwdata/core/attachments — vault item attachments
  • ./bwdata/mssql/data — the database itself
  • ./bwdata/core/aspnet-dataprotection — framework-level data protection keys, covering authentication tokens and some database columns

That last one is the one people miss. Restore the database without aspnet-dataprotection and you get a server that starts fine and then can't decrypt parts of its own data — the worst kind of broken, because it looks like it worked. For a disaster-recovery plan you actually trust, back up the entire bwdata directory; it's the vendor's own advice and it removes the guesswork.

The stack also writes automatic nightly database backups into ./bwdata/mssql/backups and keeps them for a month. Those are useful for point-in-time recovery on the same box, and useless if the box is gone — they're a convenience, not a backup strategy.

The safe way to take a copy is with the stack stopped, so the database files are consistent:

cd ~/bitwarden
./bitwarden.sh stop
sudo tar czf - bwdata \
  | gpg --symmetric --cipher-algo AES256 \
      -o "bitwarden-$(date +%F).tar.gz.gpg"
./bitwarden.sh start

Encrypt it, always — this archive is your vault, so a backup that leaks is as bad as a database that leaks. Then copy the .gpg file off the server: object storage, another machine, anywhere that survives this VPS dying. A backup that only lives on the same box dies with the box.

Automate it on a schedule, and — the part everyone skips — test a restore at least once. Stand up a throwaway server, drop the restored bwdata in place, run ./bitwarden.sh start, and confirm you can log in. If the brief stop-and-start window is a problem for you, snapshot the whole VPS volume on your provider's schedule as well; on a vault this is belt and braces, not paranoia.

Upgrades

Upgrading is two commands, in this order, because the script updates itself first:

cd ~/bitwarden
./bitwarden.sh updateself
./bitwarden.sh update

updateself fetches a newer bitwarden.sh; update pulls the new images, applies any database migrations, and recreates the containers against your unchanged data. There's also ./bitwarden.sh updatedb if you ever need to run migrations alone, and ./bitwarden.sh rebuild to regenerate the deployment config after editing config.yml.

Run a backup first, every time. Then read the release notes before a big jump — the stack is stable, but it's a real database migration, and a snapshot you can roll back to costs you nothing on the days it turns out you didn't need it.

Troubleshooting

The client says "Cannot reach server" or refuses the URL. Almost always HTTPS. Load https://vault.example.com in a browser and confirm the certificate is valid — the apps reject self-signed and expired certificates even where a browser lets you click through. Then check that DNS resolves to this box (dig +short vault.example.com), that 80 and 443 are open in ufw and in any cloud firewall your provider puts in front of it, and that the server URL in the client matches the domain you gave the installer exactly.

Containers keep restarting, or the stack never becomes healthy. Check memory first: free -m. SQL Server is the usual culprit on an undersized box, and its logs will say so — docker logs bitwarden-mssql (the container names are prefixed bitwarden-). If memory is fine, docker ps -a will show which service is cycling, and its log is the next place to look.

The admin portal at /admin won't let me in. It authenticates by emailing a login link, so this is nearly always SMTP. Confirm your address is in adminSettings__admins, confirm the mail block in global.override.env is correct, and remember that a $ in the SMTP password is not supported. Restart after any edit — the file is only read at startup.

Invitations and verification emails never arrive. Same root cause. Check the api container's logs for send failures, verify the credentials against your provider, and check whether your provider requires the replyToEmail address to be a verified sender.

The certificate didn't renew. If Bitwarden's bundled nginx is managing it, ./bitwarden.sh renewcert forces the attempt and prints the reason if it fails — usually port 80 being blocked or the A record having moved. If Caddy is in front, renewal is Caddy's job; check its logs instead.

Verification + next steps

You're done when you can: load https://vault.example.com with a valid certificate, create and verify an account (proving SMTP works), log in from a real Bitwarden client pointed at your server URL, confirm public registration is closed in a private window, reach the admin portal via an emailed link, and produce an encrypted, off-box backup of bwdata that you have restored at least once.

After that it's routine: updateself and update on a schedule you choose, backups flowing off the box, and a supported password manager running on infrastructure that answers to you. The thing underneath it that matters most is a host with dependable uptime and real volume snapshots — this is your credentials. A Hetzner instance in the 4 GB tier is the value pick; DigitalOcean is the easy button if you want snapshots and monitoring in the same dashboard; Kamatera is the one to reach for when you want to size CPU and RAM independently. See Best VPS for self-hosting for the ranked picks.

Next steps

How to self-host BitwardenBest VPS for a Password ManagerAutomatic HTTPS with CaddyRun Claude Code with Ollama on Your Own VPSDeploy Coolify on a VPSHow to Deploy AnythingLLM on a VPSHow to Deploy authentik on a VPSHow to Deploy Beszel on a VPSHow to Deploy Checkmate on a VPSHow to Deploy docker-mailserver on a VPSHow to Deploy Gatus on a VPSHow to Deploy Gitea on a VPSHow to Deploy Grafana on a VPSHow to Deploy Headscale on a VPSHow to Deploy Healthchecks on a VPSHow to Deploy Home Assistant on a VPSHow to Deploy Immich on a VPSHow to Deploy Jan on a VPSHow to Deploy LibreChat on a VPSHow to Deploy LocalAI on a VPSHow to Deploy Mailcow on a VPSHow to Deploy Mailu on a VPSHow to Deploy n8n on a VPSHow to Deploy NetBird on a VPSHow to Deploy Netdata on a VPSHow to Deploy Nextcloud on a VPSHow to Deploy Next.js to a VPSHow to Deploy Ollama on a VPSHow to Deploy Open WebUI on a VPSHow to Deploy OpenHands on a VPSHow to Deploy Pangolin on a VPSHow to Deploy Passbolt on a VPSHow to Deploy Plausible Analytics on a VPSHow to Deploy Psono on a VPSHow to Deploy Stalwart on a VPSHow to Deploy Supabase on a VPSHow to Deploy TeamPass on a VPSHow to Deploy Twenty CRM on a VPSHow to Deploy Uptime Kuma on a VPSHow to Deploy Vaultwarden on a VPSHow to Deploy wg-easy on a VPSHow to Deploy Zabbix on a VPSDocker & Compose on Ubuntu 26.04Building AI Workflows with n8nInstall Open WebUI with OllamaAdding AI-Powered Insights to Plausible AnalyticsBuilding AI-Powered Apps with Supabase and pgvector

Search SelfHost Atlas

Search apps, comparisons, guides, and categories.

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.