Files
2026-07-12 10:17:17 -04:00

3.8 KiB

Vaultwarden Deployment (rayserver)

Complete worked example for deploying Vaultwarden (Bitwarden-compatible password manager) on rayserver infrastructure.

Architecture

Browser → VPS (vault.graj-media.com:443, nginx SSL)
           ↳ Tailscale → Home server (100.93.253.36:3446, nginx SSL)
                          ↳ Vaultwarden (127.0.0.1:8812, Docker)

Docker Compose

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    volumes:
      - /mnt/seagate8tb/docker/vaultwarden:/data
    ports:
      - "127.0.0.1:8812:80"
    environment:
      - DOMAIN=https://vault.graj-media.com
      - SIGNUPS_ALLOWED=false
      - WEBSOCKET_ENABLED=true
      - LOG_FILE=/data/vaultwarden.log
      - LOG_LEVEL=warn

Key settings:

  • DOMAIN — must match the public URL for correct invite links and CORS
  • SIGNUPS_ALLOWED — enable temporarily for account creation, disable after
  • WEBSOCKET_ENABLED — required for live sync with browser extensions
  • Bind to 127.0.0.1 — all access goes through nginx

Nginx WebSocket Configuration

Vaultwarden uses WebSocket for the notifications hub. The nginx config needs a dedicated location block for /notifications/hub with Upgrade and Connection headers, plus a separate block for the negotiate endpoint:

server {
    listen 3446 ssl;
    server_name vault.graj-media.com;

    ssl_certificate /etc/letsencrypt/live/grajmedia.duckdns.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/grajmedia.duckdns.org/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    client_max_body_size 128M;

    location / {
        proxy_pass http://127.0.0.1:8812;
        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;
    }

    location /notifications/hub {
        proxy_pass http://127.0.0.1:8812;
        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;
    }

    location /notifications/hub/negotiate {
        proxy_pass http://127.0.0.1:8812;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }
}

Without the WebSocket blocks, browser extensions can't maintain a live sync connection — they'll work for manual syncs but won't push changes in real-time.

Admin Token Setup

After first deployment, set the admin token via Docker environment variable. The token MUST be hashed with argon2id:

# Generate random token
ADMIN_TOKEN=$(openssl rand -hex 32)

# Hash with argon2id using the Vaultwarden container
HASHED=$(echo -n "$ADMIN_TOKEN" | sudo docker exec -i vaultwarden /vaultwarden hash)

# Add to docker-compose.yml environment section
#   - ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$...'
# Then restart: sudo docker compose up -d

Access admin panel at https://vault.graj-media.com/admin — use the raw (unhashed) token to log in.

Post-Deploy Steps

  1. Open signups temporarily: set SIGNUPS_ALLOWED=true, restart container
  2. Create user account: visit https://vault.graj-media.com, register
  3. Disable signups: set SIGNUPS_ALLOWED=false, restart container
  4. Configure Bitwarden apps: Settings → Self-hosted → Server URL: https://vault.graj-media.com

Bitwarden Client Setup

All official Bitwarden clients support self-hosted servers:

  • Browser extension: Settings → Self-Hosted Environment → Server URL
  • Desktop app: Settings → Self-Hosted Environment
  • Mobile (iOS/Android): Settings → Self-Hosted Environment

Enter https://vault.graj-media.com as the server URL. No trailing slash.