2025-10-05

Self-Hosting a BitWarden-Compatible Password Manager Service

When you trust third parties with your password vault, there’s always some risk. Most password manager services use zero-knowledge encryption to keep your passwords safe, but they still have enough information about you to identify you, and there may be useful metadata or billing information stored. Furthermore, if the service is offline for maintenance or under a denial-of-service attack, you may have trouble accessing your passwords. All password manager services cost money if you want all the features. The free plans provide only basic functionality for a single user.

Hosting your own password vault comes with plenty of risks, too. You are solely responsible for patches, maintenance, backups, and providing support to family, co-workers or friends using your solution. I cannot overstate the seriousness and responsibility that comes with managing a service like this for other people, or the consequences of doing it poorly.

BitWarden is open-source, and there are several alternative implementations that work well. For ease of use, I’ll focus on deploying VaultWarden in Docker with an nginx front-end. My production environment is running the rust server directly on OpenBSD with relayd.

You’ll need a DNS domain, access to create DNS entries, and a public-facing server that’s accessible via HTTPS. You might be able to run this at home, but a dedicated server, VPS or cloud server might be more reliable. I’ll be using the latest Debian bookworm for this, but Ubuntu Server, Amazon Linux, etc should work fine with minor adjustments. 

You should have a DNS entry for your VaultWarden server. The examples use vault.yourdomain.com and you should adjust that accordingly.

Make sure all the packages are up to date

sudo apt update && sudo apt upgrade -y

Install packages:
sudo apt install -y docker.io nginx docker-compose certbot

Add yourself to the docker group

sudo usermod -aG docker ${USER}
newgrp docker

Create a directory for VaultWarden

mkdir vaultwarden
cd vaultwarden


This will download the latest VaultWarden docker image and then run a command inside a temporary container to generate the strong encrypted hash for the administrator login page. 

docker run --rm -it vaultwarden/server /vaultwarden hash

Create a strong password.

Create .env file based on the output of the hash generator. There are many special characters in the hash, and it's easiest to just reference this value as a variable rather than trying to get it into the docker-compose.yml file directly. Note the prefix “VAULTWARDEN_” on this line.

VAULTWARDEN_ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4…'

Create docker-compose.yml:

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    volumes:
      - ./vw-data:/data
      - /var/log/vaultwarden:/var/log/vaultwarden
    environment:
      - DOMAIN=https://vault.yourdomain.com
      - SIGNUPS_ALLOWED=true  # Set to false after creating accounts
      - SHOW_PASSWORD_HINT=false
      - ROCKET_PORT=80
      - LOG_FILE=/var/log/vaultwarden/vaultwarden.log
      - LOG_LEVEL=info
      - EXTENDED_LOGGING=true
      - IP_HEADER=X-Forwarded-For
      - ADMIN_TOKEN=${VAULTWARDEN_ADMIN_TOKEN}
    ports:
      - "127.0.0.1:8080:80"

We have a chicken-and-egg problem now. nginx will be running after installation, but we can't configure it for TLS without the certificates from LetsEncrypt, and we can't run certbot while nginx is running. Let's stop nginx and run certbot manually. Don't forget to use your correct DNS name and email address for the certbot command.

sudo systemctl stop nginx

sudo certbot certonly --standalone -d vault.yourdomain.com --non-interactive --agree-tos --email you@email.com  --redirect 

Create the nginx config file /etc/nginx/sites-available/vault 
# Redirect HTTP to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name vault.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

# HTTPS Server
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name vault.yourdomain.com;
    ssl_certificate /etc/letsencrypt/live/vault.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/vault.yourdomain.com/privkey.pem;   
    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "same-origin" always;
    
    # Proxy to Vaultwarden
    location / {
        proxy_pass http://127.0.0.1:8080;
        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;
        
        # WebSocket support for live sync
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
    
    # Increase file size for vault exports/imports & sends
    client_max_body_size 128M;
}

# WebSocket connection upgrade map
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      "";
}

Enable this site by creating a symlink to it from sites-enabled:

sudo ln -s /etc/nginx/sites-available/vault /etc/nginx/sites-enabled/vault 

Use this fun trick to replace all the host names in your docker-compose and nginx files, if you didn't edit them manually already. Obviously, replace "vault.h-i-r.net" with your own host name.

sudo sed -i 's/vault.yourdomain.com/vault.h-i-r.net/g' /etc/nginx/sites-available/vault ~/vaultwarden/docker-compose.yml
 

Start Vaultwarden and check the logs:

cd ~/vaultwarden
docker-compose up -d
docker-compose logs

Test the nginx configuration first:
sudo nginx -t 

If no errors are found, start nginx:
sudo systemctl start nginx

Browse to your vault URL and create an account. Choose a strong master password. I recommend a passphrase. Resist the urge to use your web browser’s built-in password manager to generate a password, and do not allow the web browser to store this password.

You can import passwords, generate new ones, set up multi-factor authentication, set up shared passwords and more. 

Visit the VaultWarden admin panel at your URL /admin

Log in using the password you used to generate the admin token. Here, you can tune configuration options, manage user accounts, disable new user signups, set a new admin token, etc. Generally, the default values are safe and most of the premium paid features are enabled by default.

Install the official BitWarden browser plugins, desktop and mobile apps for your platforms. When you first attempt to login, select the “self-hosted” server and enter the URL for your new VaultWarden server. Sign in with the username and master password you created. Once installed, you can also set up biometric and passkey unlock.

To back up your own personal vault, use web or desktop, and export an encrypted, password-protected json backup. This file is resistant to cracking, and is good enough to keep saved on unencrypted backups, an external drive you keep in a safe place, or even cloud storage, so long as the password is very strong. 

blog comments powered by Disqus