Pulse/docs/REVERSE_PROXY.md
rcourtman c91b7874ac 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

367 lines
9.7 KiB
Markdown

# 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:
```bash
# 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
```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.
```caddy
pulse.example.com {
reverse_proxy localhost:7655
}
```
For more control:
```caddy
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
```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
```yaml
# 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:
```yaml
# traefik-dynamic.yml
http:
routers:
pulse:
rule: "Host(`pulse.example.com`)"
service: pulse
tls: {}
services:
pulse:
loadBalancer:
servers:
- url: "http://localhost:7655"
```
## HAProxy
```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):
```yaml
# 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:
```bash
# 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):
```bash
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+):
```bash
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:**
```bash
# 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):
```javascript
// 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:
```nginx
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
```
3. **Rate limiting** for API endpoints:
```nginx
limit_req_zone $binary_remote_addr zone=pulse:10m rate=30r/s;
limit_req zone=pulse burst=50 nodelay;
```
4. **Hide proxy version**:
```nginx
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