Install Vaultwarden on Ubuntu Server

Why Ubuntu for Vaultwarden?

Ubuntu Server is the most common deployment target for self-hosted services, and Vaultwarden runs perfectly on it. This guide covers the full production setup: Docker deployment, mandatory HTTPS via a reverse proxy with Let’s Encrypt, UFW firewall lockdown, fail2ban for brute-force protection, and automated SQLite backups via cron. If you want a password vault you can actually trust in production, this is the guide.

Updated March 2026: Verified with latest Docker images and configurations.

Prerequisites

  • Ubuntu 22.04 or 24.04 LTS server (fresh or existing)
  • Docker and Docker Compose installed (guide)
  • A domain name pointed at your server’s public IP (A record)
  • 256 MB of free RAM
  • 500 MB of free disk space
  • SSH access with a sudo-capable user
  • Ports 80 and 443 open on your network (for Let’s Encrypt and HTTPS)

Install Docker

If Docker is not already installed:

sudo apt update && sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker $USER

Log out and back in for the group change to take effect.

Configure UFW Firewall

Lock down the server before deploying anything:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Docker bypasses UFW by default because it manipulates iptables directly. To fix this, edit /etc/docker/daemon.json:

{
  "iptables": false
}

Then restart Docker:

sudo systemctl restart docker

With "iptables": false, Docker will no longer punch holes through UFW. You will need to manage port exposure carefully in your Compose files. Since we are running Vaultwarden behind a reverse proxy on the same Docker network, no host port mapping is needed for Vaultwarden itself.

Docker Compose Configuration

Create your project directory:

mkdir -p ~/vaultwarden && cd ~/vaultwarden

Create a docker-compose.yml:

services:
  vaultwarden:
    image: vaultwarden/server:1.35.4
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      # REQUIRED: Set your domain (must be HTTPS)
      - DOMAIN=https://vault.example.com
      # Admin panel — generate a token with: openssl rand -base64 48
      - ADMIN_TOKEN=${ADMIN_TOKEN}
      # Disable new user signups after you create your account
      - SIGNUPS_ALLOWED=true
      # Enable WebSocket notifications for browser extension sync
      - WEBSOCKET_ENABLED=true
      # Rate limiting for login attempts
      - LOGIN_RATELIMIT_MAX_BURST=5
      - LOGIN_RATELIMIT_SECONDS=60
      # Log to stdout
      - LOG_FILE=/data/vaultwarden.log
      - LOG_LEVEL=warn
      - EXTENDED_LOGGING=true
    volumes:
      - vaultwarden-data:/data
    networks:
      - proxy
    # No port mapping — reverse proxy handles external access

  nginx-proxy:
    image: jc21/nginx-proxy-manager:2.14.0
    container_name: nginx-proxy-manager
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "81:81"    # Admin panel — restrict access after setup
    volumes:
      - npm-data:/data
      - npm-letsencrypt:/etc/letsencrypt
    networks:
      - proxy

volumes:
  vaultwarden-data:
  npm-data:
  npm-letsencrypt:

networks:
  proxy:
    name: proxy

Create a .env file:

# Generate a secure admin token
ADMIN_TOKEN=$(openssl rand -base64 48)

Generate your actual .env:

echo "ADMIN_TOKEN=$(openssl rand -base64 48)" > .env

Start the stack:

docker compose up -d

Configure the Reverse Proxy

Bitwarden clients refuse to connect over plain HTTP. HTTPS is mandatory.

  1. Open Nginx Proxy Manager at http://your-server-ip:81
  2. Default login: [email protected] / changeme (change immediately)
  3. Add a Proxy Host:
    • Domain: vault.example.com
    • Scheme: http
    • Forward Hostname: vaultwarden
    • Forward Port: 8080
    • WebSocket Support: enabled
  4. Under the SSL tab:
    • Select “Request a new SSL Certificate”
    • Enable “Force SSL”
    • Enable “HTTP/2 Support”
    • Agree to Let’s Encrypt ToS
  5. Save

Test by navigating to https://vault.example.com. You should see the Vaultwarden web vault.

First-Time Setup

  1. Go to https://vault.example.com and create your account
  2. After creating your account, disable signups by editing your .env:
# In docker-compose.yml, change:
# SIGNUPS_ALLOWED=true -> SIGNUPS_ALLOWED=false

Then restart:

docker compose up -d
  1. Access the admin panel at https://vault.example.com/admin using the token from your .env file
  2. Install a Bitwarden client (browser extension, desktop, or mobile) and connect it to https://vault.example.com

Set Up fail2ban

Protect against brute-force login attempts. Vaultwarden logs failed logins to its log file, and fail2ban watches for patterns.

Install fail2ban:

sudo apt install -y fail2ban

Create a filter at /etc/fail2ban/filter.d/vaultwarden.conf:

[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =

Create a jail at /etc/fail2ban/jail.d/vaultwarden.local:

[vaultwarden]
enabled = true
port = 80,443
filter = vaultwarden
# Path to the Vaultwarden log file inside the named volume
logpath = /var/lib/docker/volumes/vaultwarden_vaultwarden-data/_data/vaultwarden.log
maxretry = 5
bantime = 3600
findtime = 600
action = iptables-allports[name=vaultwarden, chain=FORWARD]

Restart fail2ban:

sudo systemctl restart fail2ban
sudo fail2ban-client status vaultwarden

Automated Backups via Cron

Vaultwarden uses SQLite, so backups are straightforward. Create a backup script at ~/vaultwarden/backup.sh:

#!/bin/bash
BACKUP_DIR="$HOME/vaultwarden-backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DATA_DIR="/var/lib/docker/volumes/vaultwarden_vaultwarden-data/_data"

mkdir -p "$BACKUP_DIR"

# Use sqlite3 .backup for a safe, consistent copy
sqlite3 "$DATA_DIR/db.sqlite3" ".backup '$BACKUP_DIR/db_$TIMESTAMP.sqlite3'"

# Copy attachments and other data files
cp -r "$DATA_DIR/attachments" "$BACKUP_DIR/attachments_$TIMESTAMP" 2>/dev/null
cp "$DATA_DIR/rsa_key.pem" "$BACKUP_DIR/rsa_key_$TIMESTAMP.pem" 2>/dev/null
cp "$DATA_DIR/rsa_key.pub.pem" "$BACKUP_DIR/rsa_key_pub_$TIMESTAMP.pem" 2>/dev/null

# Remove backups older than 30 days
find "$BACKUP_DIR" -type f -mtime +30 -delete
find "$BACKUP_DIR" -type d -empty -mtime +30 -delete

echo "Backup completed: $TIMESTAMP"

Make it executable and add to cron:

chmod +x ~/vaultwarden/backup.sh
crontab -e

Add this line for daily backups at 3 AM:

0 3 * * * /home/$USER/vaultwarden/backup.sh >> /home/$USER/vaultwarden/backup.log 2>&1

Ubuntu-Specific Optimization

Automatic security updates. Enable unattended-upgrades to keep Ubuntu patched:

sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

Swap space. If your server has limited RAM, add a swap file:

sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Docker log rotation. Prevent Docker logs from filling your disk. Add to /etc/docker/daemon.json:

{
  "iptables": false,
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Troubleshooting

Bitwarden client says “connection refused” or “not secure”

HTTPS is mandatory. Bitwarden clients will not authenticate over plain HTTP. Verify your reverse proxy is working and the SSL certificate is valid:

curl -I https://vault.example.com

Admin panel returns 404

Make sure you have set ADMIN_TOKEN in your environment. Without a token, the admin panel is disabled entirely.

fail2ban is not banning IPs

Check that the log path in the jail config matches the actual Docker volume path:

sudo docker volume inspect vaultwarden_vaultwarden-data | grep Mountpoint

Also verify Vaultwarden is actually writing to its log file (check LOG_FILE env var).

WebSocket notifications not working

Ensure your reverse proxy has WebSocket support enabled. In Nginx Proxy Manager, the “WebSocket Support” toggle must be on for the Vaultwarden proxy host.

Container won’t start after upgrade

Check for breaking changes in the Vaultwarden release notes. Back up your data volume before upgrading image tags.

Resource Requirements

  • RAM: ~50 MB idle, ~80 MB under active use
  • CPU: Negligible. Vaultwarden is written in Rust and extremely efficient.
  • Disk: ~50 MB for the application, plus your vault data (typically under 100 MB even with thousands of entries)

Comments