Operations

Markdown

Reverse proxy cookbook

Operator cookbook for TLS termination and WebSocket upgrades in front of Externa (Docker Compose or bare metal).

App env (always)

APP_URL=https://externa.example.com
TRUSTED_PROXIES=*
# or comma-separated proxy CIDRs
SESSION_SECURE_COOKIE=true

Browser-facing Reverb (baked into the Vite bundle — rebuild image after changes):

REVERB_HOST=externa.example.com
REVERB_PORT=443
REVERB_SCHEME=https
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

Passkeys need a secure context: HTTPS APP_URL matching Fortify WebAuthn relying party — Passkeys.

Compose defaults: app published APP_PORT→80 (prod sample often 8080), Reverb REVERB_HOST_PORT→8080 (often host 8081). Proxy to those upstreams (or to service DNS app:80 / reverb:8080 on the Compose network).

Caddy

externa.example.com {
	encode gzip

	@websockets {
		header Connection *Upgrade*
		header Upgrade websocket
	}

	# Reverb WebSockets (path depends on your Echo setup — default Laravel Reverb)
	handle /app/* {
		reverse_proxy reverb:8080
	}

	handle {
		reverse_proxy app:80
	}
}

If Echo connects on a dedicated host/port, point a second site block at reverb:8080 with TLS.

Traefik (Docker labels sketch)

services:
  app:
    labels:
      - traefik.enable=true
      - traefik.http.routers.externa.rule=Host(`externa.example.com`)
      - traefik.http.routers.externa.entrypoints=websecure
      - traefik.http.routers.externa.tls.certresolver=le
      - traefik.http.services.externa.loadbalancer.server.port=80

  reverb:
    labels:
      - traefik.enable=true
      - traefik.http.routers.externa-ws.rule=Host(`externa.example.com`) && PathPrefix(`/app`)
      - traefik.http.routers.externa-ws.entrypoints=websecure
      - traefik.http.routers.externa-ws.tls.certresolver=le
      - traefik.http.services.externa-ws.loadbalancer.server.port=8080

Ensure Traefik forwards X-Forwarded-* (default) so Laravel trusted proxies work with TRUSTED_PROXIES.

nginx

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

server {
    listen 443 ssl http2;
    server_name externa.example.com;

    # ssl_certificate …;
    # ssl_certificate_key …;

    location /app/ {
        proxy_pass http://127.0.0.1:8081;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 60s;
    }

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 600s;  # long AI / SSE if needed
    }
}

Adjust upstream ports to your APP_PORT / REVERB_HOST_PORT.

Checklist

  • [ ] APP_URL is HTTPS and matches the browser host
  • [ ] TRUSTED_PROXIES set; login/CSRF works behind the proxy
  • [ ] WebSocket upgrade reaches Reverb; Echo connects (avatar / notifications)
  • [ ] Production image rebuilt after VITE_REVERB_* changes
  • [ ] Passkeys tested on the public HTTPS origin