Reverse Proxy
For production deployments, place Bulwark behind a reverse proxy for TLS termination and custom domains.
Caddy
The simplest option  automatic HTTPS with Let's Encrypt:
mail.example.com {
reverse_proxy bulwark:3000
}
Nginx
server {
listen 443 ssl http2;
server_name mail.example.com;
ssl_certificate /etc/ssl/cert.pem;
ssl_certificate_key /etc/ssl/key.pem;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
}
}
Traefik
Using Docker labels:
bulwark:
image: ghcr.io/bulwarkmail/webmail:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.bulwark.rule=Host(`mail.example.com`)"
- "traefik.http.routers.bulwark.tls.certresolver=letsencrypt"
- "traefik.http.services.bulwark.loadbalancer.server.port=3000"
Important Notes
Required Headers
Whatever reverse proxy you use, make sure to forward these headers:
X-Forwarded-For Client IP addressX-Forwarded-Proto Original protocol (http/https)Host Original hostname
EventSource Support
Bulwark uses JMAP EventSource for real-time push notifications. Ensure your reverse proxy supports long-lived HTTP connections and does not buffer server-sent events. For Nginx, the proxy_set_header Connection 'upgrade' and proxy_http_version 1.1 directives handle this.
Session URL Rewriting
Bulwark automatically rewrites JMAP session URLs returned by the server to match the origin the client connects to. This fixes deployments where Stalwart returns an internal hostname (e.g., http://stalwart:8080) that isn't reachable from the browser.