Vaultwarden Deployment Best Practices for Docker, Reverse Proxies, and HTTPS
Place Vaultwarden behind a reverse proxy for production deployments to handle TLS termination, WebSocket upgrades, and client IP forwarding while keeping the Rust container bound to localhost.
Vaultwarden is a self-hosted Bitwarden API implementation written in Rust. While the underlying Rocket framework can serve TLS directly, the project explicitly recommends reverse proxy configurations for production environments to manage HTTPS certificates and WebSocket connectivity. Following these deployment best practices for Docker, reverse proxies, and HTTPS ensures secure TLS termination, proper live sync functionality, and reliable client IP extraction for audit logs.
Why Use a Reverse Proxy with Vaultwarden
Running Vaultwarden behind a reverse proxy provides critical production capabilities that direct TLS termination cannot match. According to the official README.md, this approach is the recommended architecture for all public-facing instances.
The key advantages include:
- Flexible TLS management using Let's Encrypt, custom certificates, or corporate PKI without modifying the Rust binary
- Reliable WebSocket upgrades required for live sync and real-time admin diagnostics
- Security header enforcement (HSTS, X-Frame-Options, CSP) at the edge
- Accurate client IP extraction through configurable header forwarding for rate limiting and audit trails
The .env.template file emphasizes that setting the DOMAIN variable with an https:// prefix is intended for use behind a reverse proxy, not for direct Rocket TLS【https://github.com/dani-garcia/vaultwarden/blob/main/.env.template#L195-L202】. Additionally, the admin diagnostics endpoint in src/api/admin.rs explicitly checks for proxy presence via environment variables and reports "uses_proxy": true when detected【https://github.com/dani-garcia/vaultwarden/blob/main/src/api/admin.rs#L20-L24】.
Docker Configuration for Production
Bind the Vaultwarden container to localhost only, leaving public exposure to your reverse proxy. This prevents direct container access while allowing the proxy to route traffic securely.
# docker-compose.yml
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
ports:
- "127.0.0.1:8080:80"
environment:
- DOMAIN=https://vaultwarden.example.com
- ADMIN_TOKEN=${ADMIN_TOKEN}
- IP_HEADER=X-Real-IP
volumes:
- ./vw-data:/data
Critical environment variables:
DOMAIN: Must match your public HTTPS URL exactly. The.env.templatenotes this is required for URL generation in invitation emails and API responses【https://github.com/dani-garcia/vaultwarden/blob/main/.env.template#L195-L199】.IP_HEADER: Configures which header Vaultwarden reads for the original client IP. The defaultX-Real-IPmatches standard reverse proxy configurations.
Reverse Proxy Configuration Examples
Choose a proxy that fits your infrastructure. All three options below support automatic HTTPS and WebSocket forwarding.
NGINX Configuration
NGINX provides fine-grained control over WebSocket handling and security headers. Use Certbot for Let's Encrypt certificates.
# /etc/nginx/conf.d/vaultwarden.conf
server {
listen 80;
server_name vaultwarden.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name vaultwarden.example.com;
ssl_certificate /etc/letsencrypt/live/vaultwarden.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vaultwarden.example.com/privkey.pem;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
The X-Real-IP header must match the IP_HEADER value in your Vaultwarden environment. The Upgrade and Connection headers are mandatory for WebSocket support.
Caddy Configuration
Caddy offers zero-configuration automatic HTTPS with a simplified configuration format.
# Caddyfile
vaultwarden.example.com {
reverse_proxy 127.0.0.1:8080 {
header_up X-Real-IP {remote_host}
}
encode gzip
}
Caddy automatically provisions and renews Let's Encrypt certificates without additional tooling.
Traefik Configuration
For Docker-native deployments, Traefik uses container labels for dynamic routing.
services:
vaultwarden:
image: vaultwarden/server:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.vaultwarden.rule=Host(`vaultwarden.example.com`)"
- "traefik.http.routers.vaultwarden.tls=true"
- "traefik.http.routers.vaultwarden.entrypoints=websecure"
- "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
- "traefik.http.middlewares.vw-headers.headers.customrequestheaders.X-Real-IP={remoteAddr}"
- "traefik.http.routers.vaultwarden.middlewares=vw-headers"
Traefik handles ACME certificate generation automatically when tls=true is specified.
Verifying Your Deployment
After running docker compose up -d, confirm the setup is functioning correctly:
- Access
https://vaultwarden.example.comand verify the browser shows a valid TLS certificate - Navigate to
/admin/diagnosticsand confirm"uses_proxy": trueappears in the JSON output, confirming the detection logic insrc/api/admin.rsrecognizes your proxy【https://github.com/dani-garcia/vaultwarden/blob/main/src/api/admin.rs#L20-L24】 - Open browser developer tools and verify WebSocket connections show
101 Switching Protocolsstatus
Security Hardening Recommendations
Beyond basic proxy configuration, enable these additional protections defined in .env.template:
ADMIN_RATELIMIT_SECONDSandADMIN_RATELIMIT_MAX_BURST: Configure brute-force protection for the admin panel. The template suggests300seconds with a burst of3attempts【https://github.com/dani-garcia/vaultwarden/blob/main/.env.template#L27-L30】EXTENDED_LOGGING: Enable temporarily insrc/util.rsvia environment variables to debug proxy header issues when client IPs appear incorrect【https://github.com/dani-garcia/vaultwarden/blob/main/.env.template#L90-L93】ALLOWED_IFRAME_ANCESTORS: Restrict vault embedding to specific domains via CSP headers if required by your security policy
Summary
- Always deploy Vaultwarden behind a reverse proxy in production for proper TLS termination and WebSocket support
- Bind the Docker container to localhost only (
127.0.0.1:8080:80) to prevent direct internet exposure - Set
DOMAINto your HTTPS URL and configureIP_HEADERto match your proxy's forwarded header - Include WebSocket upgrade headers (
UpgradeandConnection) in your proxy configuration to enable live sync - Verify proxy detection in the admin diagnostics endpoint to confirm proper configuration
Frequently Asked Questions
Can Vaultwarden handle HTTPS directly without a reverse proxy?
Yes, but it is not recommended for production. The Rocket web framework supports direct TLS via environment variables, but the README.md explicitly recommends reverse proxies for better certificate management, security header control, and WebSocket reliability. Direct TLS also complicates client IP extraction since the container sees the NAT address rather than the original visitor.
Which reverse proxy is best for Vaultwarden?
All major proxies work well. Caddy requires the least configuration for automatic HTTPS, NGINX offers the most granular control over headers and WebSocket tuning, and Traefik integrates natively with Docker Compose for dynamic service discovery. Choose based on your existing infrastructure expertise.
Why are WebSocket headers required in the proxy configuration?
Vaultwarden uses WebSockets for live sync and real-time admin diagnostics. The Upgrade: websocket and Connection: upgrade headers tell the proxy to transform the HTTP connection into a persistent bidirectional stream. Without these headers, sync functionality will fail silently or fall back to inefficient polling.
How do I confirm my reverse proxy is properly detected?
Visit the admin diagnostics page at https://your-domain.com/admin/diagnostics and check the JSON output for "uses_proxy": true. This flag is set by the diagnostics endpoint in src/api/admin.rs when it detects proxy-related environment variables or headers, confirming Vaultwarden recognizes it is running behind a proxy and will trust the IP_HEADER for client identification.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →