Files
Pulse/docs/REVERSE_PROXY.md
rcourtman ea94f544e0 docs: comprehensive v4.24.0 documentation audit and updates
Complete documentation overhaul for Pulse v4.24.0 release covering all new
features and operational procedures.

Documentation Updates (19 files):

P0 Release-Critical:
- Operations: Rewrote ADAPTIVE_POLLING_ROLLOUT.md as GA operations runbook
- Operations: Updated ADAPTIVE_POLLING_MANAGEMENT_ENDPOINTS.md with DEFERRED status
- Operations: Enhanced audit-log-rotation.md with scheduler health checks
- Security: Updated proxy hardening docs with rate limit defaults
- Docker: Added runtime logging and rollback procedures

P1 Deployment & Integration:
- KUBERNETES.md: Runtime logging config, adaptive polling, post-upgrade verification
- PORT_CONFIGURATION.md: Service naming, change tracking via update history
- REVERSE_PROXY.md: Rate limit headers, error pass-through, v4.24.0 verification
- PROXY_AUTH.md, OIDC.md, WEBHOOKS.md: Runtime logging integration
- TROUBLESHOOTING.md, VM_DISK_MONITORING.md, zfs-monitoring.md: Updated workflows

Features Documented:
- X-RateLimit-* headers for all API responses
- Updates rollback workflow (UI & CLI)
- Scheduler health API with rich metadata
- Runtime logging configuration (no restart required)
- Adaptive polling (GA, enabled by default)
- Enhanced audit logging
- Circuit breakers and dead-letter queue

Supporting Changes:
- Discovery service enhancements
- Config handlers updates
- Sensor proxy installer improvements

Total Changes: 1,626 insertions(+), 622 deletions(-)
Files Modified: 24 (19 docs, 5 code)

All documentation is production-ready for v4.24.0 release.
2025-10-20 17:20:13 +00:00

9.7 KiB

Reverse Proxy Configuration

Pulse uses WebSockets for real-time updates. Your reverse proxy MUST support WebSocket connections or Pulse will not work correctly.

Important Requirements

  1. WebSocket Support Required - Enable WebSocket proxying
  2. Proxy Headers - Forward original host and IP headers
  3. Timeouts - Increase timeouts for long-lived connections (7 days recommended)
  4. Buffer Sizes - Increase for large state updates (64KB recommended)
  5. Rate Limit Headers (v4.24.0+) - Pass through X-RateLimit-* and Retry-After headers
  6. Error Pass-through - Don't intercept 429 responses (rate limits)

Authentication with Reverse Proxy

If you're using authentication at the reverse proxy level (Authentik, Authelia, etc.), you can disable Pulse's built-in authentication to avoid double login prompts:

# In your .env file or environment
DISABLE_AUTH=true

When DISABLE_AUTH=true is set:

  • Pulse's built-in authentication is completely bypassed
  • All endpoints become accessible without authentication
  • The reverse proxy handles all authentication and authorization
  • A warning is logged on startup to confirm auth is disabled

⚠️ Warning: Only use DISABLE_AUTH=true if your reverse proxy provides authentication. Never expose Pulse directly to the internet with authentication disabled.

Nginx

server {
    listen 80;
    server_name pulse.example.com;
    
    # Redirect to HTTPS
    return 301 https://$server_name$request_uri;
}

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

    # SSL configuration
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # Proxy settings
    location / {
        proxy_pass http://localhost:7655;
        proxy_http_version 1.1;

        # Required for WebSocket
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Proxy headers
        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;

        # Timeouts for WebSocket and adaptive polling (v4.24.0+)
        proxy_connect_timeout 7d;
        proxy_send_timeout 7d;
        proxy_read_timeout 7d;

        # Disable buffering for real-time updates
        proxy_buffering off;

        # IMPORTANT (v4.24.0+): Don't intercept error responses
        # This ensures 429 rate limit responses reach the client
        proxy_intercept_errors off;

        # Increase buffer sizes for large messages
        proxy_buffer_size 64k;
        proxy_buffers 8 64k;
        proxy_busy_buffers_size 128k;
    }
    
    # API endpoints (optional, same config as above)
    location /api/ {
        proxy_pass http://localhost:7655/api/;
        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_connect_timeout 7d;
        proxy_send_timeout 7d;
        proxy_read_timeout 7d;
        proxy_buffering off;
    }
}

Caddy v2

Caddy automatically handles WebSocket upgrades when reverse proxying.

pulse.example.com {
    reverse_proxy localhost:7655
}

For more control:

pulse.example.com {
    reverse_proxy localhost:7655 {
        # Headers automatically handled by Caddy
        header_up Host {host}
        header_up X-Real-IP {remote}
        header_up X-Forwarded-For {remote}
        header_up X-Forwarded-Proto {scheme}
        
        # Increase timeouts for WebSocket
        transport http {
            dial_timeout 30s
            response_header_timeout 30s
            read_timeout 0
        }
    }
}

Apache

<VirtualHost *:443>
    ServerName pulse.example.com
    
    SSLEngine on
    SSLCertificateFile /path/to/cert.pem
    SSLCertificateKeyFile /path/to/key.pem
    
    # Enable necessary modules:
    # a2enmod proxy proxy_http proxy_wstunnel headers
    
    # WebSocket proxy
    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteCond %{HTTP:Connection} upgrade [NC]
    RewriteRule ^/?(.*) "ws://localhost:7655/$1" [P,L]
    
    # Regular HTTP proxy
    ProxyPass / http://localhost:7655/
    ProxyPassReverse / http://localhost:7655/
    
    # Preserve host headers
    ProxyPreserveHost On
    
    # Forward real IP
    RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"
    RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
    RequestHeader set X-Forwarded-Proto "https"
    
    # Disable buffering
    ProxyIOBufferSize 65536
</VirtualHost>

Traefik

# docker-compose.yml
services:
  pulse:
    image: rcourtman/pulse:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.pulse.rule=Host(`pulse.example.com`)"
      - "traefik.http.routers.pulse.tls=true"
      - "traefik.http.services.pulse.loadbalancer.server.port=7655"
      # WebSocket support is automatic in Traefik 2.x+

Or using Traefik file configuration:

# traefik-dynamic.yml
http:
  routers:
    pulse:
      rule: "Host(`pulse.example.com`)"
      service: pulse
      tls: {}
      
  services:
    pulse:
      loadBalancer:
        servers:
          - url: "http://localhost:7655"

HAProxy

frontend https
    bind *:443 ssl crt /path/to/cert.pem
    
    # ACL for Pulse
    acl host_pulse hdr(host) -i pulse.example.com
    
    # WebSocket detection
    acl is_websocket hdr(Upgrade) -i websocket
    
    # Use backend
    use_backend pulse if host_pulse

backend pulse
    # Health check
    option httpchk GET /api/health
    
    # WebSocket support
    option http-server-close
    option forwardfor
    
    # Timeouts for WebSocket
    timeout client 3600s
    timeout server 3600s
    timeout tunnel 3600s
    
    # Backend server
    server pulse1 localhost:7655 check

Cloudflare Tunnel

If using Cloudflare Tunnel (cloudflared):

# config.yml
tunnel: YOUR_TUNNEL_ID
credentials-file: /path/to/credentials.json

ingress:
  - hostname: pulse.example.com
    service: http://localhost:7655
    originRequest:
      # Enable WebSocket
      noTLSVerify: false
      connectTimeout: 30s
      # No additional config needed - WebSockets work by default
  - service: http_status:404

Testing WebSocket Connection

After configuring your reverse proxy, test that WebSockets work:

# Test basic connectivity
curl https://pulse.example.com/api/health

# Test WebSocket upgrade (should return 101 Switching Protocols)
curl -i -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  https://pulse.example.com/api/ws

v4.24.0+ Verification

Test scheduler health API (adaptive polling):

curl -s https://pulse.example.com/api/monitoring/scheduler/health | jq

Expected response:

  • enabled: true
  • Queue depth reasonable
  • No stuck circuit breakers
  • WebSocket connection remains open

Verify rate limit headers (v4.24.0+):

curl -I https://pulse.example.com/api/version

Look for headers:

HTTP/1.1 200 OK
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 499
X-RateLimit-Reset: 1698765432

Test rate limiting:

# Trigger rate limit (requires many requests)
for i in {1..600}; do curl -s https://pulse.example.com/api/health > /dev/null; done

# Should eventually see:
curl -I https://pulse.example.com/api/health
# HTTP/1.1 429 Too Many Requests
# X-RateLimit-Limit: 500
# X-RateLimit-Remaining: 0
# Retry-After: 60

Important: If you don't see rate limit headers or 429 responses are converted to 502/504, verify proxy_intercept_errors off (Nginx) or equivalent is set.

In browser console (F12):

// Test WebSocket connection
const ws = new WebSocket('wss://pulse.example.com/api/ws');
ws.onopen = () => console.log('WebSocket connected!');
ws.onmessage = (e) => console.log('Received:', e.data);
ws.onerror = (e) => console.error('WebSocket error:', e);

Common Issues

"Connection Lost" or no real-time updates

  • WebSocket upgrade not configured correctly
  • Check proxy passes Upgrade and Connection headers
  • Verify timeouts are increased for long connections

CORS errors

  • Pulse handles CORS internally
  • Don't add additional CORS headers in proxy
  • If needed, set ALLOWED_ORIGINS in Pulse configuration

502 Bad Gateway

  • Pulse not running on expected port (default 7655)
  • Check with: curl http://localhost:7655/api/health
  • Verify Pulse service: systemctl status pulse (use pulse-backend if you're on a legacy unit)

WebSocket closes immediately

  • Timeout too short in proxy configuration
  • Increase proxy_read_timeout (Nginx) or equivalent
  • Set to at least 3600s (1 hour) or more

Security Recommendations

  1. Always use HTTPS for production deployments
  2. Set proper headers to prevent clickjacking:
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
    
  3. Rate limiting for API endpoints:
    limit_req_zone $binary_remote_addr zone=pulse:10m rate=30r/s;
    limit_req zone=pulse burst=50 nodelay;
    
  4. Hide proxy version:
    proxy_hide_header X-Powered-By;
    server_tokens off;
    

Support

If WebSockets still don't work after following this guide:

  1. Check browser console for errors (F12)
  2. Verify Pulse logs: journalctl -u pulse -f
  3. Test without proxy first: http://your-server:7655
  4. Report issues: https://github.com/rcourtman/Pulse/issues