SecurityAugust 4, 20265 min read

How to Self-Host Vaultwarden Behind an Nginx Reverse Proxy

Already run Nginx? Here's how to put Vaultwarden behind it properly — real config, Let's Encrypt TLS, WebSocket support and the security headers that matter.

NBy Nxeon

If you already run Nginx as your reverse proxy, this guide shows how to put Vaultwarden behind it correctly — with a Let's Encrypt certificate, WebSocket support for live sync, and sensible security headers. It's the companion to the general how to self-host Vaultwarden guide, focused specifically on the Nginx setup that trips people up.

Why the reverse proxy matters here

Vaultwarden's browser extensions refuse to connect over plain HTTP, so TLS is mandatory. Nginx terminates HTTPS, forwards requests to the Vaultwarden container on localhost, and — importantly — proxies the WebSocket connection Vaultwarden uses for real-time sync. Get any of those wrong and the vault either won't load or won't sync. If you're new to Nginx and Let's Encrypt, set up Nginx with free SSL covers the basics this builds on.

Run Vaultwarden bound to localhost

Start Vaultwarden listening only on the loopback interface, so it's never directly exposed — Nginx is the only thing that talks to it.

mkdir -p ~/vaultwarden && cd ~/vaultwarden
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      - DOMAIN=https://vault.example.com
      - SIGNUPS_ALLOWED=true
    volumes:
      - ./vw-data:/data
    ports:
      - "127.0.0.1:8080:80"
docker compose up -d

Binding to 127.0.0.1:8080 means the container is reachable only from the host — exactly what you want behind a proxy.

Get a certificate

Install Nginx and Certbot, then obtain a certificate for your domain:

sudo apt update && sudo apt install -y nginx certbot python3-certbot-nginx
sudo certbot --nginx -d vault.example.com

Certbot provisions the certificate and sets up auto-renewal. Make sure your DNS A record already points vault.example.com at the server — see how to point a domain to your VPS.

The Nginx site config

Create /etc/nginx/sites-available/vaultwarden with a config that handles both HTTP traffic and the WebSocket upgrade:

server {
    listen 443 ssl;
    server_name vault.example.com;

    ssl_certificate     /etc/letsencrypt/live/vault.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/vault.example.com/privkey.pem;

    client_max_body_size 128M;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;

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

        # WebSocket support for live sync
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

server {
    listen 80;
    server_name vault.example.com;
    return 301 https://$host$request_uri;
}

Enable the site and reload:

sudo ln -s /etc/nginx/sites-available/vaultwarden /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
The Nxeon game-server control panel — live console, player slots, and TPS
The Nxeon game-server control panel — live console, player slots, and TPS

Verify sync works

Open https://vault.example.com, create your account, and log in with a browser extension pointed at that URL. To confirm the WebSocket is proxied correctly, change an item on one device and watch it appear on another without a manual refresh — that live update only works if the Upgrade/Connection headers above are in place.

Lock it down

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

Then set SIGNUPS_ALLOWED=false and restart once your accounts exist, enable two-factor, and back up vw-data on a schedule — the same essentials from the password manager security guide.

Harden and test the setup

A working proxy is the baseline; a few extra touches make it solid.

Restrict the admin panel. Vaultwarden's /admin page is powerful. Either leave it disabled (don't set an ADMIN_TOKEN) or protect it in Nginx by allowing only your IP:

location /admin {
    allow YOUR.IP.ADDR;
    deny all;
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
}

Tighten TLS. Certbot's defaults are sane, but you can pin modern protocols and enable OCSP stapling in the server block, then grade your endpoint with an online SSL test and confirm it reports a clean, modern configuration.

Confirm renewals. A lapsed certificate breaks every client, so verify automatic renewal works:

sudo certbot renew --dry-run

Add fail2ban (optional). Vaultwarden logs failed logins; a fail2ban jail watching that log blocks brute-force attempts at the firewall — a worthwhile layer for an internet-facing vault.

Nginx or Caddy — and where this fits

If you don't already run Nginx, Caddy achieves the same result with far less config, as shown in the general Vaultwarden guide. Choose Nginx when you already terminate other sites with it and want everything under one server, one set of logs and one TLS strategy. Either way, the fundamentals — HTTPS, closed signups, two-factor and tested backups — matter more than which proxy you pick.

Hosting other apps on the same Nginx

Once Vaultwarden is working behind Nginx, the same server can front all your other self-hosted apps — each gets its own server block on its own subdomain. This is the big advantage of running your own reverse proxy: one place to manage TLS, logs and headers for everything you host. A typical pattern is a separate site file per app in /etc/nginx/sites-available/, each proxying to a container bound to a different localhost port:

  • vault.example.com127.0.0.1:8080 (Vaultwarden)
  • cloud.example.com127.0.0.1:8081 (Nextcloud)
  • n8n.example.com127.0.0.1:5678 (n8n)

Run sudo certbot --nginx -d newapp.example.com for each new subdomain and Certbot slots the certificate straight into the config. Test with sudo nginx -t before every reload so a typo never takes all your sites down at once. Keep app containers bound to 127.0.0.1 as shown, and Nginx stays the single, controlled front door to your whole stack.

FAQ

Why does Vaultwarden need WebSocket proxying in Nginx?

Vaultwarden uses a WebSocket for real-time sync notifications. Without the Upgrade and Connection headers, the vault still works but changes won't push live to other devices.

Nginx or Caddy for Vaultwarden?

Both are fine. Caddy handles TLS automatically with less config; Nginx gives you fine-grained control and suits servers already running it. This guide is for the Nginx camp.

Do I still need Certbot renewals?

Certbot sets up automatic renewal via a systemd timer. Confirm it with sudo certbot renew --dry-run so your certificate never lapses.

Should Vaultwarden be exposed directly?

No. Bind it to 127.0.0.1 as shown so only Nginx can reach it; never publish the container port to the public internet.

Run a hardened, Nginx-fronted vault on a secure NVMe VPS with full root access. See more on the security page.

#vaultwarden#nginx#reverse-proxy#ssl#self-hosting#seobatch

Deploy your first server in under a minute

Creating an account is free and takes no card details. You pay when you deploy — choose a billing term and pay from your wallet or by card at checkout.