I self-hosted Vaultwarden so the password vault stays on my server

I self-hosted Vaultwarden so the password vault stays on my server

I wanted Bitwarden clients talking to a vault I operate, not a hosted password SaaS. Vaultwarden on Ubuntu 24.04 is one container until Nginx 502s because port 8080 is not up, or the browser extension cannot find the custom server URL. Here's that wall.

· Updated · 3 min read #self-hosted #open-source #vaultwarden #security #password-manager #deployment #docker #vps

 Vaultwarden self-hosted password manager

Caption: Vaultwarden on my VPS — Bitwarden apps sync to my /data directory, not a vendor cloud.

Introduction

I wanted family and small-team passwords on a box I patch, with the Bitwarden apps I already use. Vaultwarden is the unofficial Rust server that speaks that protocol without the official Bitwarden's resource appetite. SQLite by default, one Compose service, Nginx in front.

This is critical infrastructure even with three users. I treat TLS, backups, and restores like I would for production MySQL.

On Ubuntu 24.04 the install is famous for Nginx 502 when the container is not bound where the vhost thinks (127.0.0.1:8080 in this Compose file). The other wall: Bitwarden clients that still point at bitwarden.com because I logged in before setting the self-hosted URL. I quoted those from the wiki-style troubleshooting already in this post, bound the port to localhost, and set DOMAIN=https://vaultwarden.example.com before any real vault items.

Public signups stay off. Invites go through SMTP.

Why I picked Vaultwarden

  • Small VPS is enough; I was not going to run the full official stack for a household.
  • Official Bitwarden apps and extensions, custom server URL.
  • One container, one ./data directory, a reverse proxy.
  • Vault metadata and attachments stay on my disk, under my backup policy.
  • Organizations and collections for shared logins.
  • SMTP for invites so I never turn public registration on.

If I needed a vendor contract and compliance paperwork, I would look at official Bitwarden. I needed a lean vault I can restore.

Prerequisites

Hardware:

  • 1 CPU / 1 GB RAM for personal or family
  • 2 CPU / 2 GB if attachments and other containers share the box
  • 10 GB free to start, plus attachments and backups
  • SSD — SQLite and attachments do not belong on a dying spinning disk

Software and accounts:

  • Ubuntu 24.04 LTS with sudo
  • Domain such as vaultwarden.example.com
  • DNS A or AAAA
  • Docker + Compose
  • Nginx + Certbot
  • SMTP for invites
  • A second machine to test restores

Security notes:

  • SSH keys; sudo limited.
  • Only 80/443 public. Container port stays off UFW.
  • Signups off after first setup; invite on purpose.
  • Two-step login on every account.
  • Back up all of /data and restore once before storing real secrets.
sudo apt update
sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg git ufw nginx certbot python3-certbot-nginx

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

Installation Guide

Compose + official vaultwarden/server image. Container on 127.0.0.1:8080; Nginx terminates TLS.

1. Install Docker Engine

curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER"
newgrp docker

docker --version
docker compose version

If newgrp docker does not stick, I log out and back in.

2. Create the Project Directory

sudo mkdir -p /opt/vaultwarden/data
sudo chown -R "$USER":"$USER" /opt/vaultwarden
cd /opt/vaultwarden
chmod 700 data

data is the service: SQLite, attachments, icons, sends, crypto files. Every backup includes it.

3. Generate an Admin Token and Environment File

Long random token, password manager, not Slack.

cd /opt/vaultwarden
ADMIN_TOKEN_VALUE="$(openssl rand -base64 48 | tr -d '\n')"
read -rsp "SMTP password: " SMTP_PASSWORD_VALUE
echo

cat > .env <<EOF
DOMAIN=https://vaultwarden.example.com
ADMIN_TOKEN=${ADMIN_TOKEN_VALUE}
SIGNUPS_ALLOWED=false
INVITATIONS_ALLOWED=true
SMTP_HOST=smtp.example.com
SMTP_FROM=vaultwarden@example.com
SMTP_PORT=587
SMTP_SECURITY=starttls
SMTP_USERNAME=vaultwarden@example.com
SMTP_PASSWORD=${SMTP_PASSWORD_VALUE}
LOG_LEVEL=warn
EOF

chmod 600 .env

For a harder setup I hash the admin token with Argon2id as the Vaultwarden admin-page docs describe, then put the hash in ADMIN_TOKEN. The random token is fine for first boot; a hash is safer if .env ever leaks.

4. Create the Docker Compose File

compose.yaml:

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - ./data:/data
    ports:
      - "127.0.0.1:8080:80"
cd /opt/vaultwarden
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --since=2m vaultwarden

Local 8080 only. I do not open it in the firewall.

 Vaultwarden Docker stack architecture

Caption: Nginx on 443; Vaultwarden on localhost; /opt/vaultwarden/data is the vault.

5. Configure Nginx and HTTPS

/etc/nginx/sites-available/vaultwarden.example.com:

server {
    listen 80;
    listen [::]:80;
    server_name vaultwarden.example.com;

    client_max_body_size 128M;

    location / {
        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_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://127.0.0.1:8080;
    }
}
sudo ln -s /etc/nginx/sites-available/vaultwarden.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d vaultwarden.example.com

After Certbot, https://vaultwarden.example.com. First account, signups stay off, more users via admin invites.

Configuration

Domain and Proxy Awareness

DOMAIN is the HTTPS URL I type into Bitwarden clients. After a change:

cd /opt/vaultwarden
nano .env
docker compose up -d
docker compose logs --since=1m vaultwarden

Nginx must send Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto. Without them, login and admin links behave like the service is still localhost.

SMTP for Invites and Account Emails

Vaultwarden runs without mail; invite-only onboarding does not. After .env edits:

cd /opt/vaultwarden
docker compose up -d
docker compose logs --since=5m vaultwarden

No mail: host, port, TLS mode, user, password, SMTP_FROM, SPF/DKIM/DMARC. Providers reject From domains they have not verified.

Admin Page

https://vaultwarden.example.com/admin

Admin token from the password manager. Before invites:

  1. Public registration off.
  2. Invitations on only if I will use email.
  3. Attachment limits I can actually store.
  4. Who can create organizations.
  5. Notes in my runbook, not only in the UI.

I prefer /admin behind a VPN or extra proxy rule, not on every public IP.

 Vaultwarden security flow

Caption: HTTPS, closed signups, 2FA on accounts, admin token not in chat.

Usage

  1. Bitwarden extension, desktop, or mobile.
  2. Self-hosted server before login.
  3. https://vaultwarden.example.com as the URL.
  4. Create or accept invite.
  5. Two-step login in account settings.
  6. Import only after I reviewed the export file locally.
  7. Shared collections for family/team — not passwords in a group chat.

Before I trust it:

  • Test item syncs to a second client.
  • Small attachment appears under data/attachments.
  • Invite email arrives.
  • Organization collection shares one test item.
  • Private window login.
  • Container restart, clients still sync.

I migrate real passwords in batches. Old manager stays until the important accounts work from the new clients.

Screenshots and Visuals

Original diagrams only. Internal runbook screenshots: admin (secrets blurred), SMTP test, client server URL, collections, backup output. I do not publish vault metadata.

Where it broke

On a lab Ubuntu 24.04 box this is the failure Vaultwarden's Docker + Nginx setup is famous for.

1. Nginx 502 Bad Gateway

The vhost was up; Vaultwarden was not answering http://127.0.0.1:8080.

cd /opt/vaultwarden
docker compose ps
docker compose logs --since=2m vaultwarden

Fix: container healthy, ports still "127.0.0.1:8080:80", Nginx proxy_pass http://127.0.0.1:8080. If I published 8080:80 on all interfaces, that is a different (worse) mistake — I bind localhost only.

2. Bitwarden client cannot find the server

Extension still talked to the official cloud, or I omitted https://. Wiki-style fix: set the custom server URL before login, full HTTPS URL, no extra path. DOMAIN in .env must match that URL. Insecure-origin warnings mean Certbot did not finish or DOMAIN still starts with http://.

Uploads failing is often Nginx client_max_body_size (I set 128M) or a full disk under /opt/vaultwarden/data. Admin token "wrong" is usually a newline or quote in .env — I fix the file and docker compose up -d.

Troubleshooting

  • Invites not delivered: SMTP and verified sender, then spam folder.
  • Disk filling: attachments, sends, old backups.
  • Upgrade: release notes, backup, restore test, then pull the image.

Scaling, Securing, and Next Steps

Backups

Whole data directory, not only db.sqlite3.

cd /opt/vaultwarden
docker compose stop
sudo tar -czf /root/vaultwarden-backup-$(date +%F).tar.gz compose.yaml .env data
docker compose start
sudo sha256sum /root/vaultwarden-backup-$(date +%F).tar.gz

Encrypt it, keep an off-site copy, limit who can fetch it.

Restore on a separate host:

sudo mkdir -p /opt/vaultwarden
sudo tar -xzf /root/vaultwarden-backup-2026-07-01.tar.gz -C /opt/vaultwarden
sudo chown -R "$USER":"$USER" /opt/vaultwarden
cd /opt/vaultwarden
docker compose up -d
docker compose logs --since=2m vaultwarden

Login, attachments, client sync. Then the backup plan is real.

 Vaultwarden backup and restore checklist

Caption: /data archive, encrypted copy off-box, restore rehearsal — that is the vault.

Upgrades

cd /opt/vaultwarden
docker compose pull
docker compose up -d
docker compose logs --since=10m vaultwarden

I keep the previous image tag in notes so I can roll back. Pin a tag when this vault is the only copy of credentials.

Hardening

  • 2FA on all accounts.
  • Signups off; review pending invites.
  • Hashed admin token; network-restrict /admin if I can.
  • Watch HTTPS and container restarts.
  • Patch Ubuntu, Docker, Nginx, Vaultwarden.
  • Admin token, SMTP password, backup key in separate records.
  • Restore steps where another admin can find them.

Conclusion

Vaultwarden is running on my Ubuntu 24.04 VPS: Compose under /opt/vaultwarden, Nginx/Certbot on the hostname, container on localhost:8080, signups off, /data in the backup tarball. Bitwarden apps sync to my server.

Next I would restore that archive on a spare box, turn on 2FA for every account, and pin an image tag. The 502 was the proxy; the client wall was the self-hosted URL I set too late.

Did you hit the same wall?

I got stuck on Nginx 502 Bad Gateway because Vaultwarden was not answering 127.0.0.1:8080. Did you hit the same thing, or a different one — Bitwarden still pointing at the cloud, SMTP invites that never arrived, a quoted ADMIN_TOKEN in .env? Tell me in the comments. I read them.

Need this done on your server?

I deploy and harden Laravel/CodeCanyon apps on cPanel or VPS, and offer monthly Server Watch retainers. Hire for deploy · Care plan

References

Share:

Get new posts in your inbox

No spam. One short email per new article — practical PHP, Laravel, devops, and AI-assisted workflows.

Comments

Powered by GitHub Discussions via Giscus. A free GitHub account is required.