// DevOps

Vaultwarden: self-hosting a password manager in 30 minutes

Published on 2026-09-22

Vaultwarden — an unofficial implementation of the Bitwarden server in Rust (AGPL-3.0 license; as of September 2026 the current version is 1.37.3). It requires significantly fewer resources than the official server, works with all official Bitwarden clients, and supports most paid features. It’s a convenient option for personal use and small teams. How Vaultwarden differs from official Bitwarden and when to choose which is covered in the article “Password Managers: Part 3 — Bitwarden and Vaultwarden”.

What you’ll need

  • A VPS with Ubuntu 22.04/24.04 (1 vCPU and 1 GB RAM is usually sufficient).
  • Docker and Docker Compose installed.
  • A domain with an A record pointing to your server’s IP address.
  • An SMTP service for sending system emails (invitations, login confirmations).

1. docker-compose.yaml setup

We deploy two containers: Vaultwarden and Nginx as a reverse proxy with Let’s Encrypt certificates. HTTPS is required: without it browser Bitwarden clients won’t work with the server, because web cryptography in the browser is available only in a secure context.

Create a working directory and the file docker-compose.yaml:

yaml
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      - SIGNUPS_ALLOWED=${VAULTWARDEN_SIGNUPS_ALLOWED}
      - ADMIN_TOKEN=${VAULTWARDEN_ADMIN_TOKEN}
    volumes:
      - ./vaultwarden-data:/data

  nginx:
    image: ${NGINX_IMAGE}
    container_name: nginx-proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    environment:
      - DOMAIN=${VAULTWARDEN_DOMAIN}
    volumes:
      - ./nginx-templates:/opt/nginx-templates:ro
      - ./letsencrypt:/etc/letsencrypt
    depends_on:
      - vaultwarden

Important: ADMIN_TOKEN exposes the admin web panel at /admin. Set it as an Argon2 hash (section 2), not as plain text, and remove it from the configuration when the panel is no longer needed. A separate WEBSOCKET_ENABLED variable is no longer required: since version 1.29 WebSocket runs on the main port.


2. Environment variables (.env)

Next to docker-compose.yaml create a .env file:

env
# Basic settings
VAULTWARDEN_DOMAIN=vault.yourdomain.com
NGINX_IMAGE=your-preferred-nginx-image:latest

# Vaultwarden settings
VAULTWARDEN_SIGNUPS_ALLOWED=false
# Argon2 hash in single quotes, $ characters are not escaped
VAULTWARDEN_ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$...'

The token is set as an Argon2id hash in PHC format, not as plain text. Vaultwarden can generate the hash itself: the command will ask for the password you will later use on the /admin page.

bash
docker run --rm -it vaultwarden/server /vaultwarden hash
# lighter parameters per OWASP recommendation:
docker run --rm -it vaultwarden/server /vaultwarden hash --preset owasp

How to write the hash depends on where you put it:

  • in the .env file — as-is, in single quotes;
  • directly in the environment section of docker-compose.yaml — each $ must be doubled ($$argon2id$$v=19$$...), otherwise Compose will treat it as a variable.

If you leave the token as plain text, Vaultwarden will log a warning: “You are using a plain text ADMIN_TOKEN which is insecure”.


3. Nginx configuration

Create the directory ./nginx-templates and inside it the file vaultwarden.conf.template:

nginx
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name __VAULTWARDEN_DOMAIN__;

    ssl_certificate /etc/letsencrypt/live/__VAULTWARDEN_DOMAIN__/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/__VAULTWARDEN_DOMAIN__/privkey.pem;

    client_max_body_size 128m;

    # WebSocket for live-sync (from Vaultwarden 1.29+ uses the same port 80)
    location /notifications/hub {
        proxy_pass http://vaultwarden:80;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;
    }

    location / {
        proxy_pass http://vaultwarden:80;
        proxy_http_version 1.1;
        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_read_timeout 3600;
        proxy_send_timeout 3600;
    }
}

If the Nginx image does not support variable substitution — replace __VAULTWARDEN_DOMAIN__ with the real domain manually and rename the file to .conf.


4. Start

bash
docker compose up -d

5. SMTP configuration

Settings saved via the /admin panel are written by Vaultwarden to the file data/config.json, and for most parameters that file has priority over environment variables. SMTP is easiest to configure in the admin panel; below is how to do it without the panel by editing the file directly.

Quickly write mail settings:

python
python3 -c "
import json
with open('./vaultwarden-data/config.json') as f:
    c = json.load(f)
c.update({
    'smtp_host': 'mail.your-provider.com',
    'smtp_port': 587,
    'smtp_security': 'starttls',
    'smtp_username': 'your-username',
    'smtp_password': 'your-password',
    'smtp_from': 'no-reply@yourdomain.com',
    'smtp_from_name': 'Vaultwarden',
    '_enable_smtp': True,
})
with open('./vaultwarden-data/config.json', 'w') as f:
    json.dump(c, f, indent=2)
"

After changing the configuration:

bash
docker compose restart vaultwarden

Caveat: Ports 25 and 587 are often blocked by cloud providers. If mail isn’t being sent — try 2525 or 8025.


6. Managing signups

Vaultwarden itself allows signups by default (SIGNUPS_ALLOWED=true), so we disabled it in .env. Enable it only while adding a new user (or invite users from the /admin panel):

bash
# Open signups
sed -i 's/VAULTWARDEN_SIGNUPS_ALLOWED=false/VAULTWARDEN_SIGNUPS_ALLOWED=true/' .env
docker compose up -d --force-recreate vaultwarden

# Close after the user has created an account
sed -i 's/VAULTWARDEN_SIGNUPS_ALLOWED=true/VAULTWARDEN_SIGNUPS_ALLOWED=false/' .env
docker compose up -d --force-recreate vaultwarden

If signups are not enabled via .env — check the value of "signups_allowed" inside config.json: it takes precedence.


7. What to back up?

FileImportanceDescription
db.sqlite3CriticalThe main DB with all encrypted passwords
rsa_key.pemCriticalEncryption keys
config.jsonImportantServer configuration (SMTP, etc.)
attachments/ImportantFiles attached to notes

Never copy db.sqlite3 directly while the server is running — use the built-in command:

bash
sqlite3 ./vaultwarden-data/db.sqlite3 ".backup /backup/db.sqlite3"

Clients

All official Bitwarden clients work with your server. On first login click the gear (server settings) and set Self-hosted URL to https://vault.yourdomain.com.

Available for: Chrome, Firefox, Safari, iOS, Android, macOS, Windows, Linux and CLI.


Summary

Deploying Vaultwarden takes about half an hour and doesn’t require a powerful server. Key points: admin token must be an Argon2 hash, HTTPS is mandatory, SMTP ports must be reachable, config.json has priority over .env, and since version 1.29 WebSocket runs on the main port 80 rather than a separate 3012.

Next: Bitwarden CLI — managing passwords from the terminal and CI/CD automation.

// Reviews

Related reviews

I needed to get n8n, Redis, and the database working. I had hired another contractor before and everything kept breaking. I hired Mikhail, and the next day everything was working quickly, like clockwork!

There was a task to get n8n, redis and the database working. I had previously ordered from another contractor, it kept breaking all the time. Ordered from Mikhail, the next day everything started working fast, like …

christ_media

n8n installation on your VPS server. Configuration of n8n, Docker, AI, Telegram

2025-09-24 · ★ 5/5

Experienced buyer

Quick solution — I highly recommend Mikhail as a contractor! I tried to build a similar configuration myself and even followed AI advice, which ended up costing a lot of time and money (due to server downtime). So my advice: hire professionals — it's cheaper =) Thanks to Mikhail for his professionalism.

Quick fix for the problem, I recommend Mikhail as a contractor to everyone! I tried to assemble a similar configuration myself and following advice from neural networks, which resulted in a lot of wasted effort and …

ladohinpy

n8n installation on your VPS server. Configuration of n8n, Docker, AI, Telegram.

2025-08-25 · ★ 5/5

// Contact

Need help?

Get in touch with me and I'll help solve the problem

I reply within one business day (03:00-13:00 GMT)

Или оставьте заявку здесь:

Confirm that you are not a bot.

Write and get a quick reply