Files
smartproxy/readme.websocket-keepalive-config.md
Juergen Kunz 8347e0fec7
Some checks failed
Default (tags) / security (push) Successful in 45s
Default (tags) / test (push) Failing after 34m50s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
19.6.2
2025-06-09 22:13:56 +00:00

4.0 KiB

WebSocket Keep-Alive Configuration Guide

Quick Fix for SNI Passthrough WebSocket Disconnections

If your WebSocket connections are disconnecting every 30 seconds in SNI passthrough mode, here's the immediate solution:

const proxy = new SmartProxy({
  // Extend timeout for keep-alive connections
  keepAliveTreatment: 'extended',
  keepAliveInactivityMultiplier: 10, // 10x the base timeout
  inactivityTimeout: 14400000, // 4 hours base (40 hours with multiplier)
  
  routes: [
    {
      name: 'websocket-passthrough',
      match: { 
        ports: 443, 
        domains: ['ws.example.com', 'wss.example.com'] 
      },
      action: {
        type: 'forward',
        target: { host: 'backend', port: 443 },
        tls: { mode: 'passthrough' }
      }
    }
  ]
});

Option 2: Immortal Connections (Never Timeout)

const proxy = new SmartProxy({
  // Never timeout keep-alive connections
  keepAliveTreatment: 'immortal',
  
  routes: [
    // ... same as above
  ]
});

Option 3: Per-Route Security Settings

const proxy = new SmartProxy({
  routes: [
    {
      name: 'websocket-passthrough',
      match: { 
        ports: 443, 
        domains: ['ws.example.com'] 
      },
      action: {
        type: 'forward',
        target: { host: 'backend', port: 443 },
        tls: { mode: 'passthrough' }
      },
      security: {
        // Disable connection limits for this route
        maxConnections: 0, // 0 = unlimited
        maxConnectionsPerIP: 0 // 0 = unlimited
      }
    }
  ]
});

Understanding the Issue

Why Connections Drop at 30 Seconds

  1. WebSocket Heartbeat: The HTTP proxy's WebSocket handler sends ping frames every 30 seconds
  2. SNI Passthrough: In passthrough mode, traffic is encrypted end-to-end
  3. Can't Inject Pings: The proxy can't inject ping frames into encrypted traffic
  4. No Pong Response: Client doesn't respond to pings that were never sent
  5. Connection Terminated: After 30 seconds, connection is marked inactive and closed

Why Grace Periods Were Too Short

  • Half-zombie detection: 30 seconds (now 5 minutes for TLS)
  • Stuck connection detection: 60 seconds (now 5 minutes for TLS)
  • These were too aggressive for encrypted long-lived connections

Long-Term Solution

The fix involves:

  1. Detecting SNI Passthrough: Skip WebSocket heartbeat for passthrough connections
  2. Longer Grace Periods: 5-minute grace for encrypted connections
  3. TCP Keep-Alive: Rely on OS-level TCP keep-alive instead
  4. Route-Aware Timeouts: Different timeout strategies per route type

TCP Keep-Alive Configuration

For best results, also configure TCP keep-alive at the OS level:

Linux

# /etc/sysctl.conf
net.ipv4.tcp_keepalive_time = 600    # Start probes after 10 minutes
net.ipv4.tcp_keepalive_intvl = 60    # Probe every minute
net.ipv4.tcp_keepalive_probes = 9    # Drop after 9 failed probes

Node.js Socket Options

The proxy already enables TCP keep-alive on sockets:

  • Keep-alive is enabled by default
  • Initial delay can be configured via keepAliveInitialDelay

Monitoring

Check your connections:

const stats = proxy.getStats();
console.log('Active connections:', stats.getActiveConnections());
console.log('Connections by route:', stats.getConnectionsByRoute());

// Monitor long-lived connections
setInterval(() => {
  const connections = proxy.connectionManager.getConnections();
  for (const [id, conn] of connections) {
    const age = Date.now() - conn.incomingStartTime;
    if (age > 300000) { // 5+ minutes
      console.log(`Long-lived connection: ${id}, age: ${age}ms, route: ${conn.routeName}`);
    }
  }
}, 60000);

Summary

  • Immediate Fix: Use keepAliveTreatment: 'extended' or 'immortal'
  • Applied Fix: Increased grace periods for TLS connections to 5 minutes
  • Best Practice: Use SNI passthrough for WebSocket when you need end-to-end encryption
  • Alternative: Use TLS termination if you need application-level WebSocket features