Reverse Proxy
Running ArchVault behind a reverse proxy is required for production deployments. A reverse proxy handles TLS termination (HTTPS), so traffic between clients and your server is encrypted. Without one, credentials and session tokens are sent in plain text.
Prerequisites
Section titled “Prerequisites”- ArchVault running via Docker Compose on port
3000 - A domain name pointing to your server (e.g.
archvault.example.com) - Ports
80and443open on your firewall
How It Works
Section titled “How It Works”Client → (HTTPS :443) → Reverse Proxy → (HTTP :3000) → ArchVault containerThe reverse proxy terminates TLS and forwards requests to ArchVault over the internal Docker network. Make sure BETTER_AUTH_URL in your .env matches the public URL (e.g. https://archvault.example.com).
Traefik is a cloud-native reverse proxy that integrates with Docker via label-based configuration. It automatically discovers services and provisions TLS certificates.
Built-in Compose profile (easiest)
Section titled “Built-in Compose profile (easiest)”The production Compose file includes a Traefik service as an optional profile. This is the fastest way to get HTTPS running — no config files to create or copy.
-
Set
DOMAINandACME_EMAILin your.env:.env DOMAIN=archvault.example.comACME_EMAIL=admin@example.com -
Start the stack with the
traefikprofile:Terminal window docker compose -f compose.prod.yaml --profile traefik up -d
That’s it. Traefik automatically provisions a TLS certificate via Let’s Encrypt, redirects HTTP to HTTPS, and proxies to ArchVault. Certificate data is persisted in the letsencrypt Docker volume so renewals survive restarts.
Standalone Docker Compose setup
Section titled “Standalone Docker Compose setup”If you prefer to configure Traefik yourself rather than using the built-in profile:
services: traefik: image: traefik:v3 restart: unless-stopped command: - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--providers.docker.network=archvault" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - letsencrypt:/letsencrypt security_opt: - no-new-privileges:true networks: - archvault
app: # ... existing app config ... ports: [] # Remove host port binding — Traefik handles it labels: - "traefik.enable=true" - "traefik.http.routers.archvault.rule=Host(`archvault.example.com`)" - "traefik.http.routers.archvault.entrypoints=websecure" - "traefik.http.routers.archvault.tls.certresolver=letsencrypt" - "traefik.http.services.archvault.loadbalancer.server.port=3000" # Security headers - "traefik.http.middlewares.archvault-headers.headers.stsSeconds=31536000" - "traefik.http.middlewares.archvault-headers.headers.stsIncludeSubdomains=true" - "traefik.http.middlewares.archvault-headers.headers.stsPreload=true" - "traefik.http.middlewares.archvault-headers.headers.frameDeny=true" - "traefik.http.middlewares.archvault-headers.headers.contentTypeNosniff=true" - "traefik.http.middlewares.archvault-headers.headers.referrerPolicy=strict-origin-when-cross-origin" - "traefik.http.routers.archvault.middlewares=archvault-headers" networks: - archvault
volumes: letsencrypt:How Traefik labels work
Section titled “How Traefik labels work”| Label | Purpose |
|---|---|
traefik.enable=true | Opt this service into Traefik discovery |
traefik.http.routers.archvault.rule | Match requests by hostname |
traefik.http.routers.archvault.tls.certresolver | Use Let’s Encrypt for TLS |
traefik.http.services.archvault.loadbalancer.server.port | Forward to port 3000 |
Traefik dashboard (optional)
Section titled “Traefik dashboard (optional)”To enable the Traefik dashboard for monitoring, add:
command: # ... existing commands ... - "--api.dashboard=true" - "--api.insecure=false"labels: - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)" - "traefik.http.routers.dashboard.service=api@internal" - "traefik.http.routers.dashboard.entrypoints=websecure" - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"Nginx is the most widely used reverse proxy. TLS certificates must be managed separately — Certbot is the recommended tool for Let’s Encrypt.
Option A: Standalone install
Section titled “Option A: Standalone install”-
Install Nginx and Certbot:
Terminal window # Debian / Ubuntusudo apt install nginx certbot python3-certbot-nginx# RHEL / Fedorasudo dnf install nginx certbot python3-certbot-nginx -
Create the site configuration:
/etc/nginx/sites-available/archvault server {listen 80;server_name archvault.example.com;location / {proxy_pass http://127.0.0.1:3000;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 Upgrade $http_upgrade;proxy_set_header Connection "upgrade";}} -
Enable the site and obtain a certificate:
Terminal window sudo ln -s /etc/nginx/sites-available/archvault /etc/nginx/sites-enabled/sudo nginx -t && sudo systemctl reload nginxsudo certbot --nginx -d archvault.example.comCertbot automatically modifies the config to add TLS and sets up auto-renewal.
Option B: Docker Compose sidecar
Section titled “Option B: Docker Compose sidecar”Add Nginx as a service in your compose.prod.yaml:
services: nginx: image: nginx:alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro - ./certs:/etc/nginx/certs:ro networks: - archvault
app: # ... existing app config ... ports: [] # Remove host port binding — Nginx handles it networks: - archvaultupstream archvault { server app:3000;}
server { listen 80; server_name archvault.example.com; return 301 https://$host$request_uri;}
server { listen 443 ssl; http2 on; server_name archvault.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_certificate_key /etc/nginx/certs/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5;
# Security headers add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options DENY always; add_header Referrer-Policy strict-origin-when-cross-origin always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
location / { proxy_pass http://archvault; 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 Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
# Timeouts proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; }}Caddy automatically obtains and renews TLS certificates via Let’s Encrypt — no manual setup needed.
Standalone Caddyfile
Section titled “Standalone Caddyfile”-
Install Caddy on your server (install guide).
-
Create a
Caddyfile:Caddyfile archvault.example.com {reverse_proxy localhost:3000encode gzip zstdheader {X-Content-Type-Options nosniffX-Frame-Options DENYReferrer-Policy strict-origin-when-cross-originStrict-Transport-Security "max-age=31536000; includeSubDomains; preload"-Server}} -
Start Caddy:
Terminal window caddy start
Docker Compose sidecar
Section titled “Docker Compose sidecar”Add Caddy as a service in your compose.prod.yaml:
services: caddy: image: caddy:2-alpine restart: unless-stopped ports: - "80:80" - "443:443" - "443:443/udp" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data - caddy_config:/config networks: - archvault
app: # ... existing app config ... ports: [] # Remove host port binding — Caddy handles it networks: - archvault
volumes: caddy_data: caddy_config:archvault.example.com { reverse_proxy app:3000
encode gzip zstd
header { X-Content-Type-Options nosniff X-Frame-Options DENY Referrer-Policy strict-origin-when-cross-origin Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" -Server }}Verifying Your Setup
Section titled “Verifying Your Setup”After configuring your reverse proxy, verify everything works:
# Check HTTPS is workingcurl -I https://archvault.example.com
# Check health endpoint through the proxycurl https://archvault.example.com/api/health
# Verify TLS certificateopenssl s_client -connect archvault.example.com:443 -servername archvault.example.com < /dev/null 2>/dev/null | openssl x509 -noout -datesYou should see:
- HTTP 200 from the health endpoint
- A valid TLS certificate
Strict-Transport-Securityin the response headers
Common Issues
Section titled “Common Issues”| Issue | Solution |
|---|---|
| 502 Bad Gateway | ArchVault container isn’t running or isn’t on the same Docker network |
| Certificate errors | Ensure your domain’s DNS A record points to the server’s public IP |
| Mixed content warnings | Set BETTER_AUTH_URL to your https:// URL in .env |
| WebSocket errors | Ensure Upgrade and Connection headers are forwarded (see Nginx config) |