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.
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
- WebSocket Support Required - Enable WebSocket proxying
- Proxy Headers - Forward original host and IP headers
- Timeouts - Increase timeouts for long-lived connections (7 days recommended)
- Buffer Sizes - Increase for large state updates (64KB recommended)
- Rate Limit Headers (v4.24.0+) - Pass through
X-RateLimit-*andRetry-Afterheaders - 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
UpgradeandConnectionheaders - 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_ORIGINSin 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(usepulse-backendif 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
- Always use HTTPS for production deployments
- Set proper headers to prevent clickjacking:
add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; - Rate limiting for API endpoints:
limit_req_zone $binary_remote_addr zone=pulse:10m rate=30r/s; limit_req zone=pulse burst=50 nodelay; - Hide proxy version:
proxy_hide_header X-Powered-By; server_tokens off;
Support
If WebSockets still don't work after following this guide:
- Check browser console for errors (F12)
- Verify Pulse logs:
journalctl -u pulse -f - Test without proxy first:
http://your-server:7655 - Report issues: https://github.com/rcourtman/Pulse/issues