Reverse Proxies
Securely expose Sesame with HTTPS using reverse proxies and tunnels
A reverse proxy terminates TLS and provides HTTPS access to Sesame. This guide covers popular options for self-hosted environments.
Traditional Reverse Proxies
These run alongside Sesame on your server and handle TLS termination, load balancing, and routing.
nginx
nginx is a battle-tested option with wide community support.
server {
listen 80;
server_name sesame.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name sesame.example.com;
ssl_certificate /etc/letsencrypt/live/sesame.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sesame.example.com/privkey.pem;
# Modern TLS configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
location / {
proxy_pass http://localhost:13531;
proxy_http_version 1.1;
# Required for WebSocket connections
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
# Required for SSE (Server-Sent Events) streaming
proxy_buffering off;
proxy_read_timeout 86400s;
# Forward client info
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;
}
}Enable the site and reload nginx:
sudo ln -s /etc/nginx/sites-available/sesame /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxUse Certbot to automatically obtain and renew Let's Encrypt certificates.
Caddy
Caddy automatically provisions TLS certificates from Let's Encrypt with zero configuration.
sesame.example.com {
reverse_proxy localhost:13531 {
# Disable buffering for SSE streaming
flush_interval -1
}
}That's it! Caddy handles HTTPS automatically. Run with:
caddy runOr use Docker Compose alongside Sesame:
services:
sesame:
image: ghcr.io/jakejarvis/sesame:latest
# ... your existing config
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
volumes:
caddy-data:
caddy-config:Traefik
Traefik automatically discovers services via Docker labels and provisions certificates.
services:
traefik:
image: traefik:v3.4
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
sesame:
image: ghcr.io/jakejarvis/sesame:latest
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.sesame.rule=Host(`sesame.example.com`)"
- "traefik.http.routers.sesame.entrypoints=websecure"
- "traefik.http.routers.sesame.tls.certresolver=letsencrypt"
- "traefik.http.services.sesame.loadbalancer.server.port=13531"
volumes:
- sesame-data:/app/data
- sesame-sandboxes:/app/sandboxes
environment:
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
BASE_URL: https://sesame.example.com
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
volumes:
sesame-data:
sesame-sandboxes:Update admin@example.com with your email for Let's Encrypt notifications.
Zero-Config Tunnels (Recommended)
These options are the safest way to access Sesame from anywhere without opening ports on your firewall or configuring DNS. They create outbound-only connections to edge networks that proxy traffic to your local service.
Tailscale Funnel
Tailscale Funnel exposes your local Sesame instance to the internet through Tailscale's network. No open ports, no firewall rules, automatic HTTPS.
Advantages:
- Zero firewall configuration (outbound connections only)
- Automatic TLS certificates
- Built-in access controls via Tailscale ACLs
- Works behind NAT, CGNAT, and corporate firewalls
Quick Setup
-
Install Tailscale on the machine running Sesame
-
Enable Funnel in your Tailscale admin console under DNS > Funnel
-
Start Sesame with Funnel:
# Expose Sesame on port 443 (recommended)
tailscale funnel --bg 13531Your instance is now available at https://<machine-name>.<tailnet-name>.ts.net
Docker Setup
Run Tailscale as a sidecar container:
services:
tailscale:
image: tailscale/tailscale:latest
hostname: sesame
environment:
- TS_AUTHKEY=${TS_AUTHKEY} # Generate at https://login.tailscale.com/admin/settings/keys
- TS_STATE_DIR=/var/lib/tailscale
- TS_SERVE_CONFIG=/config/serve.json
volumes:
- tailscale-state:/var/lib/tailscale
- ./tailscale:/config
cap_add:
- NET_ADMIN
- SYS_MODULE
restart: unless-stopped
sesame:
image: ghcr.io/jakejarvis/sesame:latest
network_mode: service:tailscale
depends_on:
- tailscale
volumes:
- sesame-data:/app/data
- sesame-sandboxes:/app/sandboxes
environment:
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
BASE_URL: https://sesame.<tailnet-name>.ts.net
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
volumes:
tailscale-state:
sesame-data:
sesame-sandboxes:{
"TCP": {
"443": {
"HTTPS": true
}
},
"Web": {
"sesame.<tailnet-name>.ts.net:443": {
"Handlers": {
"/": {
"Proxy": "http://127.0.0.1:13531"
}
}
}
},
"AllowFunnel": {
"sesame.<tailnet-name>.ts.net:443": true
}
}<tailnet-name> with your Tailscale tailnet name (found in admin console).Cloudflare Tunnel
Cloudflare Tunnel (formerly Argo Tunnel) creates a secure outbound connection from your server to Cloudflare's edge network.
Advantages:
- No open inbound ports required
- Automatic TLS with your own domain
- DDoS protection included
- Can add Cloudflare Access for additional authentication
Prerequisites
- A domain on Cloudflare (free plan works)
cloudflaredCLI installed
Quick Setup
- Authenticate with Cloudflare:
cloudflared tunnel login- Create a tunnel:
cloudflared tunnel create sesame- Create the config file:
tunnel: <TUNNEL-UUID>
credentials-file: /root/.cloudflared/<TUNNEL-UUID>.json
ingress:
- hostname: sesame.example.com
service: http://localhost:13531
originRequest:
noTLSVerify: true
- service: http_status:404- Route DNS to the tunnel:
cloudflared tunnel route dns sesame sesame.example.com- Run the tunnel:
cloudflared tunnel run sesameDocker Setup
services:
cloudflared:
image: cloudflare/cloudflared:latest
restart: unless-stopped
command: tunnel run
environment:
- TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
sesame:
image: ghcr.io/jakejarvis/sesame:latest
restart: unless-stopped
volumes:
- sesame-data:/app/data
- sesame-sandboxes:/app/sandboxes
environment:
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
BASE_URL: https://sesame.example.com
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
volumes:
sesame-data:
sesame-sandboxes:Get your CLOUDFLARE_TUNNEL_TOKEN from the Zero Trust dashboard under Networks > Tunnels > Create a tunnel.
Comparison
| Feature | nginx/Caddy/Traefik | Tailscale Funnel | Cloudflare Tunnel |
|---|---|---|---|
| Open ports required | Yes (80, 443) | No | No |
| Custom domain | Yes | No (uses ts.net) | Yes |
| Auto TLS | Caddy/Traefik only | Yes | Yes |
| Works behind NAT | No | Yes | Yes |
| DDoS protection | No | Limited | Yes |
| Setup complexity | Medium | Low | Medium |
| Cost | Free | Free | Free |
Security Recommendations
- Always use HTTPS - Never expose Sesame over plain HTTP
- Set
BASE_URL- Must match your public URL for auth to work correctly - Use strong secrets - Generate
BETTER_AUTH_SECRETwithopenssl rand -base64 32andENCRYPTION_KEYwithopenssl rand -hex 32 - Consider access controls - Use Tailscale ACLs or Cloudflare Access to restrict who can reach your instance
- Keep software updated - Regularly update Sesame, your reverse proxy, and tunnel software