The Problem
WebSocket close code 1006 means the connection was closed abnormally without a proper close frame. This happens due to network interruptions, server restarts, proxy timeouts, or load balancer issues.
Symptoms
- WebSocket disconnects randomly without warning
- Real-time features stop working (chat, notifications, live updates)
- No close event data is available
- Reconnecting sometimes works, sometimes fails
Real Error Event
ws.onclose = (event) => {
console.log('Code:', event.code); // 1006
console.log('Reason:', event.reason); // "" (empty)
console.log('WasClean:', event.wasClean); // false
};Common Causes
Cause 1: Proxy Timeout (Nginx)
# Default proxy_read_timeout is 60 seconds
# If no data is sent for 60s, nginx closes the connectionCause 2: Load Balancer Idle Timeout
AWS ALB has a default idle timeout of 60 seconds. If no data flows, it closes the connection.
Cause 3: Server Restart or Crash
When the server restarts, all WebSocket connections are dropped with code 1006.
How to Fix It
Fix 1: Heartbeat/Ping-Pong
```javascript // Client function createWebSocket(url) { const ws = new WebSocket(url); let pingInterval;
ws.onopen = () => { // Send ping every 30 seconds pingInterval = setInterval(() => { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'ping' })); } }, 30000); };
ws.onclose = () => { clearInterval(pingInterval); // Reconnect after 3 seconds setTimeout(() => createWebSocket(url), 3000); };
return ws; } ```
```javascript // Server (Node.js with ws library) wss.on('connection', (ws) => { ws.isAlive = true;
ws.on('pong', () => { ws.isAlive = true; });
ws.on('message', (data) => { const msg = JSON.parse(data); if (msg.type === 'ping') { ws.send(JSON.stringify({ type: 'pong' })); } }); });
// Server-side ping check setInterval(() => { wss.clients.forEach(ws => { if (!ws.isAlive) return ws.terminate(); ws.isAlive = false; ws.ping(); }); }, 30000); ```
Fix 2: Nginx WebSocket Configuration
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400s; # 24 hours
proxy_send_timeout 86400s;
}Fix 3: Exponential Backoff Reconnection
```javascript class ReconnectingWebSocket { constructor(url) { this.url = url; this.reconnectAttempts = 0; this.maxReconnectAttempts = 10; this.connect(); }
connect() { this.ws = new WebSocket(this.url); this.ws.onclose = () => { if (this.reconnectAttempts < this.maxReconnectAttempts) { const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000); this.reconnectAttempts++; setTimeout(() => this.connect(), delay); } }; this.ws.onopen = () => { this.reconnectAttempts = 0; // Reset on successful connection }; } } ```
Fix 4: Handle Browser Tab Visibility
document.addEventListener('visibilitychange', () => {
if (!document.hidden && ws.readyState !== WebSocket.OPEN) {
ws = new WebSocket(url); // Reconnect when tab becomes visible
}
});