🇯🇵 Tokyo is live! 🚀 Launch your VPS and enjoy 2 months off — use code KONNICHIWA50 🎉 Get Started Today →

Self-Hosted Password Manager on a VPS: Vaultwarden Setup and Hardening

Encrypted Vaultwarden service protected on a VPS server

Vaultwarden lets you run a Bitwarden-compatible password vault on infrastructure you control. That control also makes you responsible for HTTPS, host security, updates, monitoring, and recoverable backups. A working login page is only the beginning.

This guide deploys Vaultwarden on Ubuntu 24.04 with Docker Compose, binds the container to loopback, and places Nginx with a TLS certificate in front of it. You will create the first account, close public registration, harden access, back up every required data class, and test the recovery path. For a broader product comparison, read the VPS.us guide to self-hosting a password manager.

âš¡ Spin up a Premium VPS in 2 minutes
17 locations worldwide
NVMe  Â·  Unmetered 1 Gbps  Â·  Full root access  Â·  From $10/mo

Plan the Vaultwarden Deployment

The supported Vaultwarden container stores persistent state under /data. The official project recommends HTTPS and a reverse proxy because the web vault needs a secure browser context. In this design, only Nginx listens publicly on ports 80 and 443; Docker publishes Vaultwarden only on 127.0.0.1:8000.

  • DNS: create a dedicated name such as vault.example.com and point it to the VPS.
  • Host: use a maintained 64-bit Ubuntu release, key-based SSH, and a non-root administrator.
  • Application: keep Vaultwarden data on a persistent host directory and pin a reviewed container version.
  • Network: bind the application port to loopback and expose only the reverse proxy.
  • Recovery: keep encrypted copies of the SQLite backup, attachments, keys, and configuration away from the VPS.

Vaultwarden is relatively lightweight, but there is no universal minimum for every team. Size the VPS for concurrent users, attachments, backup staging, logs, and the reverse proxy, then monitor real CPU, memory, storage, and latency. Keep enough free disk space for at least one local backup artifact before it is copied off-host.

Secure SSH Before Installing the Application

Reverse proxy, encrypted Vaultwarden service, and database connected on a VPS

Create a named administrator, copy an SSH public key into that account, and verify a second login before disabling password or root access. Keep the provider console available while changing SSH and firewall rules so a typo does not strand the server.

sudo adduser vaultadmin
sudo usermod -aG sudo vaultadmin
sudo install -d -m 700 -o vaultadmin -g vaultadmin /home/vaultadmin/.ssh
sudo install -m 600 -o vaultadmin -g vaultadmin /root/.ssh/authorized_keys /home/vaultadmin/.ssh/authorized_keys
sudo apt update
sudo apt full-upgrade -y
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

From another terminal, confirm ssh vaultadmin@VPS_IP works with the key. Only then set PermitRootLogin no and PasswordAuthentication no in an SSH configuration drop-in, validate with sudo sshd -t, and reload SSH. The VPS server optimization guide covers additional operating-system checks.

Install Docker From Its Official Repository

Docker’s current Ubuntu instructions support Ubuntu 24.04 and use an ASCII-armored signing key plus a docker.sources file. Install the Engine and Compose plugin from that repository, then run the disposable hello-world test.

sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl status docker --no-pager
sudo docker run --rm hello-world

Do not assume UFW controls every Docker-published port. Docker documents that published container ports can bypass UFW rules. The loopback mapping used below is therefore an important boundary, not cosmetic configuration. Also treat membership in the docker group as root-equivalent access; this guide keeps sudo docker in its commands. See the VPS.us Docker VPS hosting guide for more container-hosting considerations.

Create the Vaultwarden Compose Project

As of this review, Vaultwarden 1.37.1 is the latest release. Pin that reviewed version instead of silently tracking latest, and repeat the release review whenever you upgrade. The official Compose example uses DOMAIN, a persistent /data mount, and a loopback-only port.

sudo install -d -m 750 -o vaultadmin -g vaultadmin /opt/vaultwarden
cd /opt/vaultwarden
cat > .env <<'EOF'
VAULTWARDEN_TAG=1.37.1
VW_DOMAIN=https://vault.example.com
SIGNUPS_ALLOWED=true
EOF
chmod 600 .env
cat > compose.yaml <<'YAML'
services:
  vaultwarden:
    image: vaultwarden/server:${VAULTWARDEN_TAG}
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      DOMAIN: ${VW_DOMAIN}
      SIGNUPS_ALLOWED: ${SIGNUPS_ALLOWED}
    volumes:
      - ./vw-data:/data
    ports:
      - "127.0.0.1:8000:80"
YAML
sudo docker compose config
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
sudo ss -lntp | grep 8000

The socket check should show 127.0.0.1:8000, never 0.0.0.0:8000. The initial SIGNUPS_ALLOWED=true is temporary so you can create the first account after HTTPS is working. Do not share the URL until registration has been closed again.

Put Nginx and HTTPS in Front

Gateway and access-control layers protecting a Vaultwarden server

Install Nginx and Certbot, then create a server block that proxies to the loopback port. Vaultwarden’s current proxy examples preserve the forwarded headers and support WebSocket upgrades on the same upstream.

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      "";
}
upstream vaultwarden_backend {
    server 127.0.0.1:8000;
    keepalive 2;
}
server {
    listen 80;
    listen [::]:80;
    server_name vault.example.com;
    client_max_body_size 128M;
    location / {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $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;
        proxy_pass http://vaultwarden_backend;
    }
}

Save that configuration as /etc/nginx/sites-available/vaultwarden, enable it, validate Nginx, and request the certificate only after the DNS record resolves to this VPS.

sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
sudo ln -s /etc/nginx/sites-available/vaultwarden /etc/nginx/sites-enabled/vaultwarden
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d vault.example.com
curl -I https://vault.example.com
sudo certbot renew --dry-run

Replace vault.example.com everywhere with the real hostname. A successful browser load is not enough: verify the certificate name, HTTPS redirect, container state, and application logs before creating the account.

Create the First Account and Close Registration

Open the HTTPS URL, create the first user, and confirm you can sign in from an official Bitwarden client configured for the self-hosted server. Enable two-step login for the account and store its recovery material outside this Vaultwarden instance.

Then edit /opt/vaultwarden/.env, change SIGNUPS_ALLOWED=true to SIGNUPS_ALLOWED=false, and recreate the service. Re-read the effective configuration instead of assuming the edit was loaded.

cd /opt/vaultwarden
sed -i 's/^SIGNUPS_ALLOWED=true$/SIGNUPS_ALLOWED=false/' .env
sudo docker compose config | grep SIGNUPS_ALLOWED
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail=100 vaultwarden

If you enable the optional /admin page, do not use a plain reusable password as ADMIN_TOKEN. Vaultwarden’s current guidance supports generating an Argon2 PHC string with docker run --rm -it vaultwarden/server:1.37.1 /vaultwarden hash. Store the resulting hash in a root-readable environment file, protect the admin route with an additional network or proxy control, and omit the admin page entirely if you do not need it.

Harden and Monitor the Running Service

Security is a set of maintained boundaries. Keep Vaultwarden on loopback, expose only Nginx, leave SSH key-only, review login and proxy logs, and apply operating-system security updates through a controlled process. Do not publish the container port to every interface merely to simplify testing.

  • Verify sudo ss -lntp after every network or Compose change.
  • Keep SIGNUPS_ALLOWED=false unless you are deliberately onboarding a user.
  • Require strong master passwords and two-step login for each account.
  • Monitor sudo docker compose logs, Nginx errors, disk use, certificate renewal, and backup results.
  • Test from outside the VPS that only the intended public ports answer.

Back Up Every Required Data Class

Encrypted Vaultwarden backup copied to two separate storage targets

With the default SQLite backend, most vault state is in db.sqlite3, but attachments are separate files. Vaultwarden’s data directory can also contain Send attachments, RSA keys, and config.json. A database-only copy is therefore not a complete recovery set.

Vaultwarden 1.32.1 and later include a database backup subcommand. Run it first so SQLite creates a consistent snapshot, then archive the required non-database files while excluding the live SQLite database and its WAL files. Encrypt the archive before copying it off-host because configuration can contain secrets.

cd /opt/vaultwarden
sudo install -d -m 700 backups
sudo docker exec vaultwarden /vaultwarden backup
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
sudo tar \
  --exclude='vw-data/db.sqlite3' \
  --exclude='vw-data/db.sqlite3-wal' \
  --exclude='vw-data/db.sqlite3-shm' \
  --exclude='vw-data/icon_cache' \
  -czf "backups/vaultwarden-${STAMP}.tar.gz" \
  vw-data
sudo tar -tzf "backups/vaultwarden-${STAMP}.tar.gz" | head
sudo sha256sum "backups/vaultwarden-${STAMP}.tar.gz"

Move the encrypted result to independent storage with retention and access controls. Do not call a file on the same VPS a backup. The VPS.us guide to self-hosted backup software explains additional off-site options.

Test Restore and Upgrade Procedures

A backup is unproven until it restores into an isolated test deployment. Stop the test container, extract the archive into an empty data directory, rename the selected built-in database snapshot to db.sqlite3, and ensure stale db.sqlite3-wal and db.sqlite3-shm files are absent before startup. Confirm accounts, organizations, attachments, and client sync, then record the test date and result.

Use the same discipline for upgrades. Read the release notes, create and export a fresh recovery set, update VAULTWARDEN_TAG to the reviewed version, pull the image, recreate the service, and repeat HTTPS, login, sync, attachment, log, and backup checks. Keep the prior image tag and verified backup until the maintenance window is complete.

Frequently Asked Questions

Is Vaultwarden the official Bitwarden server?

No. Vaultwarden is an independent Bitwarden-compatible server implementation. Use official Bitwarden clients, but report Vaultwarden server problems to the Vaultwarden project rather than Bitwarden support.

Why bind Vaultwarden to 127.0.0.1?

The loopback binding prevents the application port from listening on every host interface. Public traffic reaches Nginx, which handles HTTPS and forwards requests locally. This is especially important because Docker warns that published ports can bypass UFW rules.

Can I back up only db.sqlite3?

No. The database contains most state, but attachments are stored separately. A complete recovery plan also considers Send attachments, RSA keys, and configuration. Use a consistent SQLite backup and protect the entire required recovery set.

Should Vaultwarden update automatically to latest?

No for a production password vault. Pin a reviewed version and upgrade during a maintenance window after reading release notes and producing a verified backup. Automatic surprise changes reduce your ability to test and roll back safely.
Facebook
Twitter
LinkedIn

Table of Contents

Get started today

With VPS.US VPS Hosting you get all the features, tools

Image