4.0 KiB
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:
Option 1: Extended Keep-Alive Treatment (Recommended)
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
- WebSocket Heartbeat: The HTTP proxy's WebSocket handler sends ping frames every 30 seconds
- SNI Passthrough: In passthrough mode, traffic is encrypted end-to-end
- Can't Inject Pings: The proxy can't inject ping frames into encrypted traffic
- No Pong Response: Client doesn't respond to pings that were never sent
- 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:
- Detecting SNI Passthrough: Skip WebSocket heartbeat for passthrough connections
- Longer Grace Periods: 5-minute grace for encrypted connections
- TCP Keep-Alive: Rely on OS-level TCP keep-alive instead
- 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