Vaultwarden behind Caddy
Config + gotchas checked against the official Vaultwarden docs and the issues people actually hit · 2026-06-07
A Caddy reverse-proxy setup for Vaultwarden, with automatic HTTPS on vault.example.com. Copy it, swap the domain — the gotchas that trip people up are solved below.
New to reverse proxies? Start with the Caddy + Docker guide.
# Caddyfile for Vaultwarden
# Replace vault.example.com with your real domain. Caddy issues TLS automatically.
vault.example.com {
encode zstd gzip
header {
Strict-Transport-Security "max-age=31536000;"
}
reverse_proxy /notifications/hub vaultwarden:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
}
reverse_proxy vaultwarden:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
}
}
Replace vault.example.com with your domain. Your DNS A record must already point at the server.
Gotchas
What trips people up
WebSocket notifications never fire — clients only update on manual sync.
Vaultwarden serves real-time sync over `/notifications/hub`. That path must be reverse-proxied to the WebSocket explicitly; the Caddyfile in this stack already adds the route.
Clients reject login with "We could not process your request".
Bitwarden clients require a valid public TLS certificate — self-signed is refused. Caddy issues a real Let’s Encrypt cert automatically once your DNS A record points at the server and ports 80/443 are reachable.
Deploy
How to deploy
- Create the shared proxy network once: `docker network create proxy`.
- Start the stack: `docker compose up -d`.
- Caddy obtains the TLS certificate on first request — confirm DNS for your domain resolves to this server and ports 80/443 are open.
Runnable
Full runnable stack
Vaultwarden + Caddy + the shared network, in one compose file — so it runs on the first try, not after you stitch the proxy in yourself. Save the Caddyfile above next to it.
# docker-compose.yml — Vaultwarden + Caddy, ready to run.
# Pin caddy to a digest for production: caddy:2.8-alpine@sha256:<digest>
# 1. Save the Caddyfile above as ./Caddyfile
# 2. docker network create proxy
# 3. docker compose up -d
services:
vaultwarden:
image: docker.io/vaultwarden/server:1.32.5-alpine@sha256:76d46d32ba4120b022e0a69487f9fd79fc52e2765b1650c5c51a5dd912a3c288
restart: unless-stopped
environment:
DOMAIN: https://${VAULTWARDEN_HOST}
SIGNUPS_ALLOWED: "false"
ADMIN_TOKEN: ${ADMIN_TOKEN}
WEBSOCKET_ENABLED: "true"
SENDS_ALLOWED: "true"
EMERGENCY_ACCESS_ALLOWED: "true"
PASSWORD_ITERATIONS: "600000"
SHOW_PASSWORD_HINT: "false"
LOG_FILE: /data/vaultwarden.log
LOG_LEVEL: warn
ROCKET_PORT: "8080"
volumes:
- vaultwarden-data:/data
healthcheck:
test:
- CMD
- wget
- -qO-
- http://127.0.0.1:8080/alive
interval: 30s
timeout: 5s
retries: 3
networks:
- default
- proxy
caddy:
image: caddy:2.8-alpine
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
networks:
- proxy
volumes:
vaultwarden-data: {}
caddy-data: {}
caddy-config: {}
networks:
proxy:
external: true