BitWarden self-hosted with Nginx Reverse Proxy

Bitwarden is a very good password manager which can be self-hosted. I prefer to keep my most private data on my own server. Yesterday, I found out, that my Bitwarden server has issues regarding the websocket technology.

Because i wasted a lot of time to find a working Nginx configuration for bitwarden, here is what I have done.

BitWarden comes as a docker container and can fetch valid Let’s encrypt certificates if you choose during installation. I have already established my certificate workflow and therefore choosed no. The TLS-encryption is being done with a NGINX reverse proxy running on my bare metal Debian.

I don’t explain here how to obtain Let’s encrypt certificates via ACME. The certificates are just there.

First, take a look into bwdata/docker/docker-compose.yaml to find out which ports are used.

  nginx:
    image: bitwarden/nginx:2023.7.0
    container_name: bitwarden-nginx
    restart: always
    depends_on:
      - web
      - admin
      - api
      - identity
    ports:
      - '9696:8080'
      - '4343:8443'

This is the Nginx instance inside the docker container. It listens to ports 9696 and 4343. You need this port numbers now to configure your reverse proxy on your physical machine.

server {
  listen [::]:443 ssl http2;
  listen 443 ssl http2;
  server_name vault.example.com;
  ssl_certificate /etc/letsencrypt/live/vault.example.com/fullchain.pem; # managed by Certbot
  ssl_certificate_key /etc/letsencrypt/live/vault.example.com/privkey.pem; # managed by Certbot

  # Add headers to serve security related headers
  add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
  add_header X-Content-Type-Options nosniff;
  add_header X-Frame-Options "SAMEORIGIN";
  add_header X-XSS-Protection "1; mode=block";
  add_header X-Robots-Tag none;
  add_header X-Download-Options noopen;
  add_header X-Permitted-Cross-Domain-Policies none;

  root /var/www/bitwarden/;

  #  security settings to reach A+
  ssl_ciphers 'define SSL ciphers here for more security';
  ssl_dhparam /etc/ssl/private/dhparams.pem;
  server_tokens off;

  # Logs
  access_log /var/log/nginx/bitwarden-access.log;
  error_log /var/log/nginx/bitwarden-error.log;

  # important for send feature
  client_max_body_size 1G;

  location / {

     proxy_pass        http://localhost:9696;
     proxy_redirect    off;
     proxy_set_header Host $http_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 X-Forwarded-Protocol $scheme;
     proxy_set_header X-Url-Scheme $scheme;
     # 26.07.
     proxy_http_version 1.1;
     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection $connection_upgrade;


  }
}
# we're in the http context here
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}
upstream websocket {
     server localhost:4343;
}
#  http to https redirection
server {
    if ($host = vault.example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot
    listen 80;
    listen [::]:80;
    server_name vault.example.com;
    return 301 https://$host$request_uri;
}

First, I missed the websocket stuff, BitWarden itself seems to work then but you might notice many errors in the Browser Console.

You can generate secure TLS params with this tool.

Hope this helps somebody.