18 KiB
In this document, <SERVER> refers to the IP or domain where you access vaultwarden. If both the reverse proxy and vaultwarden are running on the same system, simply use localhost.
By default, vaultwarden listens on port 80 for web (REST API) traffic and on port 3012 for WebSocket traffic (if WebSocket notifications are enabled). The reverse proxy should be configured to terminate SSL/TLS connections (preferably on port 443, the standard port for HTTPS). The reverse proxy then passes incoming client requests to vaultwarden on port 80 or 3012 as appropriate, and upon receiving a response from vaultwarden, passes that response back to the client.
Note that when you put vaultwarden behind a reverse proxy, the connections between the reverse proxy and vaultwarden are typically assumed to be going through a secure private network, and thus do not need to be encrypted. The examples below assume you are running in this configuration, in which case you should not enable the HTTPS functionality built into vaultwarden (i.e., you should not set the ROCKET_TLS environment variable). If you do, connections will fail since the reverse proxy is using HTTP to connect to vaultwarden, but you're configuring vaultwarden to expect HTTPS.
It's common to use Docker Compose to link containerized services together (e.g., vaultwarden and a reverse proxy). See Using Docker Compose for an example of this.
Caddy 1.x (deprecated)
Caddy can also automatically enable HTTPS in some circumstances, check the docs.
:443 {
tls ${SSLCERTIFICATE} ${SSLKEY}
# or 'tls self_signed' to generate a self-signed certificate
# This setting may have compatibility issues with some browsers
# (e.g., attachment downloading on Firefox). Try disabling this
# if you encounter issues.
gzip
# The negotiation endpoint is also proxied to Rocket
proxy /notifications/hub/negotiate <SERVER>:80 {
transparent
}
# Notifications redirected to the websockets server
proxy /notifications/hub <SERVER>:3012 {
websocket
}
# Proxy the Root directory to Rocket
proxy / <SERVER>:80 {
transparent
}
}
Caddy 2.x
Caddy 2 can automatically enable HTTPS in some circumstances, check the docs.
In the Caddyfile syntax, {$VAR} denotes the value of the environment variable VAR.
If you prefer, you can also directly specify a value instead of substituting an env var value.
{$DOMAIN}:443 {
log {
level INFO
output file {$LOG_FILE} {
roll_size 10MB
roll_keep 10
}
}
# Uncomment this if you want to get a cert via ACME (Let's Encrypt or ZeroSSL).
# tls {$EMAIL}
# Or uncomment this if you're providing your own cert. You would also use this option
# if you're running behind Cloudflare.
# tls {$SSL_CERT_PATH} {$SSL_KEY_PATH}
# This setting may have compatibility issues with some browsers
# (e.g., attachment downloading on Firefox). Try disabling this
# if you encounter issues.
encode gzip
# Uncomment to allow access to the admin interface only from local networks
# @insecureadmin {
# not remote_ip 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8
# path /admin*
# }
# redir @insecureadmin /
# Notifications redirected to the websockets server
reverse_proxy /notifications/hub <SERVER>:3012
# Proxy everything else to Rocket
reverse_proxy <SERVER>:80 {
# Send the true remote IP to Rocket, so that vaultwarden can put this in the
# log, so that fail2ban can ban the correct IP.
header_up X-Real-IP {remote_host}
}
}
Nginx (by shauder)
server {
listen 443 ssl http2;
server_name vault.*;
# Specify SSL config if using a shared one.
#include conf.d/ssl/ssl.conf;
# Allow large attachments
client_max_body_size 128M;
location / {
proxy_pass http://<SERVER>:80;
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;
}
location /notifications/hub {
proxy_pass http://<SERVER>:3012;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /notifications/hub/negotiate {
proxy_pass http://<SERVER>:80;
}
# Optionally add extra authentication besides the ADMIN_TOKEN
# If you don't want this, leave this part out
location /admin {
# See: https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/
auth_basic "Private";
auth_basic_user_file /path/to/htpasswd_file;
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://<SERVER>:80;
}
}
Nginx with sub-path (by BlackDex)
In this example vaultwarden will be available via https://bitwarden.example.tld/vault/
If you want to use any other sub-path, like bitwarden or secret-vault you should change /vault/ in the example below to match.
For this to work you need to configure your DOMAIN variable to match so it should look like:
; Add the sub-path! Else this will not work!
DOMAIN=https://bitwarden.example.tld/vault/
# Define the server IP and ports here.
upstream vaultwarden-default { server 127.0.0.1:8080; }
upstream vaultwarden-ws { server 127.0.0.1:3012; }
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name vaultwarden.example.tld;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name vaultwarden.example.tld;
# Specify SSL Config when needed
#ssl_certificate /path/to/certificate/letsencrypt/live/vaultwarden.example.tld/fullchain.pem;
#ssl_certificate_key /path/to/certificate/letsencrypt/live/vaultwarden.example.tld/privkey.pem;
#ssl_trusted_certificate /path/to/certificate/letsencrypt/live/vaultwarden.example.tld/fullchain.pem;
client_max_body_size 128M;
## Using a Sub Path Config
# Path to the root of your installation
location /vault/ {
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-default;
}
location /vault/notifications/hub/negotiate {
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-default;
}
location /vault/notifications/hub {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://vaultwarden-ws;
}
# Optionally add extra authentication besides the ADMIN_TOKEN
# If you don't want this, leave this part out
location ^~ /vault/admin {
# See: https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/
auth_basic "Private";
auth_basic_user_file /path/to/htpasswd_file;
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-default;
}
}
Nginx (by ypid)
Ansible inventory example that uses DebOps to configure nginx as a reverse proxy for vaultwarden. I choose to go with the PSK in the URL for additional security to not expose the API to everyone on the Internet because the client apps do not support client certificates yet (I tested it). Note: Using subpath/PSK requires to patch the source code and recompile, ref: https://github.com/dani-garcia/vaultwarden/issues/241#issuecomment-436376497. /admin is untested. For general discussion about subpath hosting for security refer to: https://github.com/debops/debops/issues/1233
bitwarden__fqdn: 'vault.example.org'
nginx__upstreams:
- name: 'bitwarden'
type: 'default'
enabled: True
server: 'localhost:8000'
nginx__servers:
- name: '{{ bitwarden__fqdn }}'
filename: 'debops.bitwarden'
by_role: 'debops.bitwarden'
favicon: False
root: '/usr/share/vaultwarden/web-vault'
location_list:
- pattern: '/'
options: |-
deny all;
- pattern: '= /ekkP9wtJ_psk_changeme_Hr9CCTud'
options: |-
return 307 $scheme://$host$request_uri/;
## All the security HTTP headers would then need to be set by nginx as well.
# - pattern: '/ekkP9wtJ_psk_changeme_Hr9CCTud/'
# options: |-
# alias /usr/share/vaultwarden/web-vault/;
- pattern: '/ekkP9wtJ_psk_changeme_Hr9CCTud/'
options: |-
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 X-Forwarded-Port 443;
proxy_pass http://bitwarden;
## Do not use the icons features as long as it reveals all domains from
## our credentials to the server.
- pattern: '/ekkP9wtJ_psk_changeme_Hr9CCTud/icons/'
options: |-
access_log off;
log_not_found off;
deny all;
Nginx (NixOS)(by tklitschi)
Example NixOS nginx config. For more Information about NixOS Deployment see Deployment Wiki page.
{ config, ... }:
{
security.acme.acceptTerms = true;
security.acme.email = "me@example.com";
security.acme.certs = {
"vw.example.com" = {
group = "vaultwarden";
keyType = "rsa2048";
allowKeysForGroup = true;
};
};
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
virtualHosts = {
"vw.example.com" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://localhost:8812"; #changed the default rocket port due to some conflict
proxyWebsockets = true;
};
locations."/notifications/hub" = {
proxyPass = "http://localhost:3012";
proxyWebsockets = true;
};
locations."/notifications/hub/negotiate" = {
proxyPass = "http://localhost:8812";
proxyWebsockets = true;
};
};
};
};
}
Apache (by fbartels)
Remember to enable mod_proxy_wstunnel and mod_proxy_http, for example with: a2enmod proxy_wstunnel and a2enmod proxy_http.
<VirtualHost *:443>
SSLEngine on
ServerName bitwarden.$hostname.$domainname
SSLCertificateFile ${SSLCERTIFICATE}
SSLCertificateKeyFile ${SSLKEY}
SSLCACertificateFile ${SSLCA}
${SSLCHAIN}
ErrorLog \${APACHE_LOG_DIR}/bitwarden-error.log
CustomLog \${APACHE_LOG_DIR}/bitwarden-access.log combined
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /notifications/hub(.*) ws://<SERVER>:3012/$1 [P,L]
ProxyPass / http://<SERVER>:80/
ProxyPreserveHost On
ProxyRequests Off
RequestHeader set X-Real-IP %{REMOTE_ADDR}s
</VirtualHost>
Apache in a sub-location (by ss89)
Modify your docker start-up to include the sub-location.
; Add the sub-location! Else this will not work!
DOMAIN=https://$hostname.$domainname/$sublocation/
Ensure you have the websocket proxy module loaded somewhere in your apache config. It can look something like:
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so`
On some OS's you can use a2enmod, for example with: a2enmod proxy_wstunnel and a2enmod proxy_http.
<VirtualHost *:443>
SSLEngine on
ServerName $hostname.$domainname
SSLCertificateFile ${SSLCERTIFICATE}
SSLCertificateKeyFile ${SSLKEY}
SSLCACertificateFile ${SSLCA}
${SSLCHAIN}
ErrorLog \${APACHE_LOG_DIR}/error.log
CustomLog \${APACHE_LOG_DIR}/access.log combined
<Location /$sublocation/> #adjust here if necessary
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /notifications/hub(.*) ws://<SERVER>:3012/$1 [P,L]
ProxyPass http://<SERVER>:80/$sublocation/
ProxyPreserveHost On
RequestHeader set X-Real-IP %{REMOTE_ADDR}s
</Location>
</VirtualHost>
Traefik v1 (docker-compose example)
labels:
- traefik.enable=true
- traefik.docker.network=traefik
- traefik.web.frontend.rule=Host:bitwarden.domain.tld
- traefik.web.port=80
- traefik.hub.frontend.rule=Host:bitwarden.domain.tld;Path:/notifications/hub
- traefik.hub.port=3012
- traefik.hub.protocol=ws
Traefik v2 (docker-compose example by hwwilliams)
Traefik v1 labels migrated to Traefik v2
labels:
- traefik.enable=true
- traefik.docker.network=traefik
- traefik.http.routers.bitwarden-ui.rule=Host(`bitwarden.domain.tld`)
- traefik.http.routers.bitwarden-ui.service=bitwarden-ui
- traefik.http.services.bitwarden-ui.loadbalancer.server.port=80
- traefik.http.routers.bitwarden-websocket.rule=Host(`bitwarden.domain.tld`) && Path(`/notifications/hub`)
- traefik.http.routers.bitwarden-websocket.service=bitwarden-websocket
- traefik.http.services.bitwarden-websocket.loadbalancer.server.port=3012
Migrated labels plus HTTP to HTTPS redirect
These labels assume that the entrypoints defined in Traefik for port 80 and 443 are 'web' and 'websecure' respectively.
These labels also assume you already have a default certificates resolver defined in Traefik.
labels:
- traefik.enable=true
- traefik.docker.network=traefik
- traefik.http.middlewares.redirect-https.redirectScheme.scheme=https
- traefik.http.middlewares.redirect-https.redirectScheme.permanent=true
- traefik.http.routers.bitwarden-ui-https.rule=Host(`bitwarden.domain.tld`)
- traefik.http.routers.bitwarden-ui-https.entrypoints=websecure
- traefik.http.routers.bitwarden-ui-https.tls=true
- traefik.http.routers.bitwarden-ui-https.service=bitwarden-ui
- traefik.http.routers.bitwarden-ui-http.rule=Host(`bitwarden.domain.tld`)
- traefik.http.routers.bitwarden-ui-http.entrypoints=web
- traefik.http.routers.bitwarden-ui-http.middlewares=redirect-https
- traefik.http.routers.bitwarden-ui-http.service=bitwarden-ui
- traefik.http.services.bitwarden-ui.loadbalancer.server.port=80
- traefik.http.routers.bitwarden-websocket-https.rule=Host(`bitwarden.domain.tld`) && Path(`/notifications/hub`)
- traefik.http.routers.bitwarden-websocket-https.entrypoints=websecure
- traefik.http.routers.bitwarden-websocket-https.tls=true
- traefik.http.routers.bitwarden-websocket-https.service=bitwarden-websocket
- traefik.http.routers.bitwarden-websocket-http.rule=Host(`bitwarden.domain.tld`) && Path(`/notifications/hub`)
- traefik.http.routers.bitwarden-websocket-http.entrypoints=web
- traefik.http.routers.bitwarden-websocket-http.middlewares=redirect-https
- traefik.http.routers.bitwarden-websocket-http.service=bitwarden-websocket
- traefik.http.services.bitwarden-websocket.loadbalancer.server.port=3012
HAproxy (by BlackDex)
Add these lines to your haproxy configuration.
frontend vaultwarden
bind 0.0.0.0:80
option forwardfor header X-Real-IP
http-request set-header X-Real-IP %[src]
default_backend vaultwarden_http
use_backend vaultwarden_ws if { path_beg /notifications/hub } !{ path_beg /notifications/hub/negotiate }
backend vaultwarden_http
# Enable compression if you want
# compression algo gzip
# compression type text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript
server vwhttp 0.0.0.0:8080
backend vaultwarden_ws
server vwws 0.0.0.0:3012
HAproxy (by @williamdes)
Add these lines to your HAproxy configuration.
backend static-success-default
mode http
errorfile 503 /usr/local/etc/haproxy/static/index.static.default.html
errorfile 200 /usr/local/etc/haproxy/static/index.static.default.html
frontend http-in
bind *:80
bind *:443 ssl crt /acme.sh/domain.tld/domain.tld.pem
option forwardfor header X-Real-IP
http-request set-header X-Real-IP %[src]
default_backend static-success-default
# Define hosts
acl host_bitwarden_domain_tld hdr_dom(Host) -i bitwarden.domain.tld
## figure out which one to use
use_backend vaultwarden_http if host_bitwarden_domain_tld !{ path_beg /notifications/hub } or { path_beg /notifications/hub/negotiate }
use_backend vaultwarden_ws if host_bitwarden_domain_tld { path_beg /notifications/hub } !{ path_beg /notifications/hub/negotiate }
backend vaultwarden_http
# Enable compression if you want
# compression algo gzip
# compression type text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript
# You can use the container hostname if you are using haproxy with docker-compose
server vw_http 0.0.0.0:8080
backend vaultwarden_ws
# You can use the container hostname if you are using haproxy with docker-compose
server vw_ws 0.0.0.0:3012