Files
pulse/docs/REVERSE_PROXY.md
T
rcourtman 8a99b16d6c docs: correct customer docs that drifted from the source
Audited the customer-facing docs against the code: about 5,200 concrete
claims (env vars, endpoints, flags, defaults, ports, UI labels), plus every
environment variable the code reads. Each discrepancy was re-verified
against the source before editing, and every edit was reviewed again.

What users would have hit following the old text:
- UI paths and labels that no longer exist (Settings > Relay, Platform
  connections, Access Control, SSO Providers, Create Organization) and
  Patrol mode names (now Ask first, Safe auto-fix, Autopilot).
- Wrong values and behaviour: LOG_MAX_AGE=0 does not disable cleanup,
  Patrol evidence budget is 10 calls and 12 turns, ALLOW_UNPROTECTED_EXPORT
  covers private networks too, PULSE_AUDIT_ASYNC defaults on.
- Agent install: install.sh ignores PULSE_ENABLE_KUBERNETES and
  PULSE_ENABLE_PROXMOX, rejects --disable-auto-update, and the token
  fallback lives in the state directory.
- API: audit endpoints answer 402 on Community, onboarding QR is GET,
  type=container is rejected, several routes need admin plus a scope.
- HTTP_REDIRECT_PORT was not documented anywhere.

Docs are mirrored under frontend-modern/public/docs, so each change is
applied to both copies. The tests that pinned the old wording (including
three that asserted claims the code contradicts) are updated alongside.

Contract-Neutral: Customer docs corrected to match shipped behaviour; no runtime, API or contract change.
2026-10-11 13:59:56 +01:00

7.0 KiB

🔄 Reverse Proxy Setup

Pulse uses WebSockets for real-time updates. Your proxy MUST support WebSockets.

Before configuring the proxy

Use HTTPS at the public proxy and keep Pulse's backend private. The examples below are configuration fragments, not complete TLS or authentication setups. Keep Pulse authentication enabled; TLS termination does not authenticate users. For identity supplied by an IdP/proxy, follow the separate proxy authentication guide, including its Header Trust Boundary.

A same-origin reverse proxy needs no CORS exception. Keep the UI and API on the same public origin rather than adding * to repair a login or routing error. For a separate trusted browser app, follow the exact-origin CORS checks.

Forwarded HTTPS headers are trusted only from a configured immediate peer. Set PULSE_TRUSTED_PROXY_CIDRS in Pulse's deployment configuration to the proxy address as seen by Pulse, not the browser's address or the public hostname. For a proxy on the same host connecting to 127.0.0.1:7655, use:

PULSE_TRUSTED_PROXY_CIDRS=127.0.0.1/32

Use ::1/128 instead if that connection actually uses IPv6 loopback. A Docker proxy normally reaches Pulse from its private container/network address, not loopback; trust only that proxy's fixed address or a narrowly isolated proxy network. Do not trust a whole LAN or all Docker networks to make a warning go away. Wildcard ranges 0.0.0.0/0 and ::/0 are rejected at startup.

At the public edge, replace client-supplied forwarded scheme, host and client-IP headers with values established by the proxy; do not append an untrusted value. With multiple proxy hops, configure the upstream trust boundary on each hop and trust only the final peer in Pulse. PULSE_TRUSTED_NETWORKS is not a substitute for proxy trust, and proxy trust is not an authentication bypass. See configuration overrides.

⚡ Quick Configs

Nginx

Inside your HTTPS server block, for a single public-edge proxy:

location / {
    proxy_pass http://127.0.0.1:7655;
    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-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port $server_port;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Scheme "";
    proxy_set_header Forwarded "";

    # Critical for WebSockets
    proxy_read_timeout 86400; # 24h
}

Caddy

Caddy handles the WebSocket upgrade and sets forwarded client-IP, host and scheme headers automatically. The removals below discard other client-supplied forwarding metadata. Pulse still needs the matching trusted-peer setting above:

pulse.example.com {
    reverse_proxy 127.0.0.1:7655 {
        header_up -Forwarded
        header_up -X-Forwarded-Scheme
        header_up -X-Forwarded-Port
    }
}

Traefik (Docker Compose)

These labels assume an existing HTTPS entrypoint named websecure and a configured certificate. Keep Pulse on the private proxy network; do not publish its backend port publicly. Apply trusted-peer configuration to Pulse separately. Keep Traefik's forwardedHeaders.insecure disabled; upstream forwarded headers need their own narrow trusted-IP configuration, not blanket client trust.

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.pulse.rule=Host(`pulse.example.com`)"
  - "traefik.http.routers.pulse.entrypoints=websecure"
  - "traefik.http.routers.pulse.tls=true"
  - "traefik.http.routers.pulse.service=pulse"
  - "traefik.http.services.pulse.loadbalancer.server.port=7655"

Apache

Inside a TLS-enabled virtual host, with the required proxy, rewrite and headers modules enabled:

ProxyPreserveHost On
ProxyAddHeaders On
RequestHeader unset X-Forwarded-For
RequestHeader unset X-Forwarded-Host
RequestHeader unset X-Forwarded-Port
RequestHeader unset X-Real-IP
RequestHeader set X-Forwarded-Proto "https"
RequestHeader unset X-Forwarded-Scheme
RequestHeader unset Forwarded
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://127.0.0.1:7655/$1" [P,L]

ProxyPass / http://127.0.0.1:7655/
ProxyPassReverse / http://127.0.0.1:7655/

⚠️ Common Issues

"HTTPS: HTTP only" in Security Posture

If your reverse proxy terminates SSL but Pulse shows "HTTPS: HTTP only" in Settings → Security:

Pulse's secure-cookie and request-origin logic recognises direct TLS, or X-Forwarded-Proto: https from an immediate peer in PULSE_TRUSTED_PROXY_CIDRS; a header from any other peer is ignored for those decisions, even when the browser used HTTPS. Adding the header alone is not sufficient. The Security Posture HTTPS indicator is different: it is true when the request has direct TLS or any X-Forwarded-Proto: https header, whatever the peer. A passing indicator therefore does not prove that proxy trust is configured correctly, and "HTTP only" means neither TLS nor that header reached Pulse.

Check the proxy's actual backend connection address and the effective Pulse setting first, then check that the TLS proxy sends exactly https. For Nginx, keep proxy_set_header X-Forwarded-Proto $scheme inside the HTTPS server's location; do not label a plain-HTTP request as HTTPS. Caddy normally needs no header override. Any Caddy header_up override belongs inside its reverse_proxy block, not directly in the site block.

After applying configuration through your normal deployment path, sign in through the real HTTPS URL and recheck Security Posture and live dashboard updates. If using SSO, check its redirect/callback on that same URL. A successful /api/health request alone does not verify authentication, WebSockets or proxy trust. Do not disable authentication, certificate verification or origin checks, or expose the backend, to test this. Share only redacted configuration and HTTP/error status, not cookies, proxy secrets or a full network export.

Other Issues

  • "Connection lost" (Workloads) or "Disconnected", "Reconnecting...", "Sync reconnecting" or "Backend healthy" in the header status badge: the live data stream is not connected, which can happen when the WebSocket upgrade fails. Check the proxy's Upgrade and Connection headers.
  • 502 Bad Gateway: Check Pulse's actual listener and the proxy's upstream address, port and network reachability; this does not by itself prove Pulse is stopped. Container localhost refers to that container, not another service.
  • CORS Errors: Do not add CORS headers in the proxy; Pulse handles them. Set Settings → System → Network → Allowed Origins or use ALLOWED_ORIGINS if needed.
  • OIDC redirects fail: Ensure X-Forwarded-Proto is set (see above).
  • Wrong client IPs: Check the immediate peer and header replacement above. Do not broaden proxy trust or change authentication to repair attribution.