Websocket Heartbeats
title: "WebSocket Heartbeats and Keep-Alive" description: "Learn how to implement WebSocket heartbeat mechanisms for detecting dead connections, maintaining proxy connections, and ensuring reliable communication." weight: 23 date: 2026-06-28 lastmod: 2026-06-28 tags: ["apis", "websocket"]
Heartbeats (ping/pong frames) are essential for maintaining WebSocket connections. They detect dead connections, prevent proxy timeouts, and ensure the connection is still alive without requiring application data exchange.
## What You'll Learn
- Ping/pong frame mechanism
- Detecting stale connections
- Proxy keep-alive configuration
- Heartbeat interval best practices
- Implementing custom heartbeat logic
## Why It Matters
Without heartbeats, half-open connections go undetected, wasting server resources. Proxies and load balancers may also close idle connections, causing unexpected disconnections during quiet periods.
## Real-World Use
A real-time analytics dashboard sends heartbeats every 30 seconds. When a user closes their laptop, the heartbeat fails, and the server cleans up the connection within 60 seconds, freeing resources for active users.
## Flow Chart
```mermaid
sequenceDiagram
participant C as Client
participant S as Server
Note over C,S: Normal operation
C->>S: Data frames
S->>C: Data frames
Note over C,S: Heartbeat interval
S->>C: Ping frame
C->>S: Pong frame
Note over C,S: Dead connection detected
S->>C: Ping frame
Note over C: No response
S->>C: Ping frame (retry)
Note over C: No response
S->>S: Close connection
S->>C: Close frame
Code Examples
Example 1: Server-Side Heartbeat with ws Library
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', (ws) => {
ws.isAlive = true;
// Handle pong responses
ws.on('pong', () => {
ws.isAlive = true;
});
ws.on('close', () => {
clearInterval(ws.heartbeatInterval);
});
});
// Heartbeat interval
const heartbeatInterval = setInterval(() => {
server.clients.forEach((ws) => {
if (ws.isAlive === false) {
console.log('Terminating dead connection');
return ws.terminate();
}
ws.isAlive = false;
ws.ping();
});
}, 30000);
// Clean up interval on server close
server.on('close', () => {
clearInterval(heartbeatInterval);
});
console.log('WebSocket server with heartbeats on port 8080');
Expected output: Server pings all clients every 30 seconds. Clients that do not respond with pong within the interval are terminated.
Example 2: Custom Heartbeat with Timing
const WebSocket = require('ws');
class HeartbeatServer {
constructor(options = {}) {
this.pingInterval = options.pingInterval || 25000;
this.pongTimeout = options.pongTimeout || 10000;
this.server = new WebSocket.Server({ port: options.port || 8080 });
this.clients = new Map();
this.server.on('connection', (ws) => this.handleConnection(ws));
this.startHeartbeat();
}
handleConnection(ws) {
const clientData = {
lastPong: Date.now(),
missedPongs: 0,
pingTimer: null,
};
this.clients.set(ws, clientData);
console.log('Client connected. Total:', this.clients.size);
ws.on('pong', () => {
clientData.lastPong = Date.now();
clientData.missedPongs = 0;
if (clientData.pingTimer) {
clearTimeout(clientData.pingTimer);
clientData.pingTimer = null;
}
});
ws.on('close', () => {
this.clients.delete(ws);
console.log('Client disconnected. Total:', this.clients.size);
});
}
startHeartbeat() {
setInterval(() => {
const now = Date.now();
this.clients.forEach((data, ws) => {
const timeSinceLastPong = now - data.lastPong;
if (timeSinceLastPong > this.pingInterval + this.pongTimeout) {
console.log('No pong received, terminating connection');
this.clients.delete(ws);
ws.terminate();
return;
}
if (timeSinceLastPong > this.pingInterval - 1000) {
ws.ping();
console.log('Sending ping');
}
});
}, 5000);
}
}
const server = new HeartbeatServer({
pingInterval: 25000,
pongTimeout: 10000,
port: 8080,
});
Expected output: Adaptive heartbeat server tracks last pong time and terminates connections that miss their pong window.
Example 3: Client-Side Heartbeat
class HeartbeatClient {
constructor(url, options = {}) {
this.url = url;
this.pingInterval = options.pingInterval || 20000;
this.onDisconnect = options.onDisconnect || (() => {});
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.lastMessage = Date.now();
this.heartbeatTimer = null;
this.ws.onopen = () => {
console.log('Connected');
this.startHeartbeat();
};
this.ws.onmessage = () => {
this.lastMessage = Date.now();
};
this.ws.onclose = (event) => {
this.stopHeartbeat();
if (!event.wasClean) {
this.onDisconnect();
}
};
this.ws.onping = () => {
console.log('Received server ping, sending pong');
this.ws.pong();
};
}
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
const idleTime = Date.now() - this.lastMessage;
if (idleTime > this.pingInterval) {
// Send application-level heartbeat
this.ws.send(JSON.stringify({
type: 'heartbeat',
timestamp: Date.now(),
}));
}
}, 5000);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
send(data) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(data);
}
}
}
// Usage
const client = new HeartbeatClient('wss://server.example.com/ws', {
pingInterval: 30000,
onDisconnect: () => {
console.log('Connection lost, reconnecting...');
// Trigger reconnection logic
},
});
Expected output: Client sends application-level heartbeats when idle, monitors connection health, and triggers reconnection on failure.
Common Mistakes
| Mistake | Explanation |
|---|---|
| Heartbeat interval too short | Frequent pings waste bandwidth and CPU; 20-30 seconds is reasonable |
| Heartbeat interval too long | Long intervals delay dead connection detection; proxies may timeout before heartbeat |
| Not handling ping frames on client | Clients must respond to ping frames with pong frames to keep connections alive |
| Relying only on TCP keepalive | TCP keepalive defaults are too long (2 hours); implement application-level heartbeats |
| Not differentiating between client and server heartbeats | Both sides should participate in heartbeat to detect one-sided failures |
Practice Questions
- What is the purpose of WebSocket ping/pong frames?
- How do heartbeats prevent proxy timeout issues?
- What is a good heartbeat interval for most applications?
- How do you detect a dead WebSocket connection?
- What is the difference between protocol-level ping and application-level heartbeat?
Challenge
Build a WebSocket server that monitors connection health using heartbeats, logs heartbeat statistics (success rate, latency), and automatically scales down server resources when many connections are idle. Include a dashboard showing connection health metrics.
FAQ
Mini Project
Build a WebSocket connection health monitor. Create a server that tracks heartbeat statistics per connection (ping latency, last pong time, missed pongs). Create a client that displays connection quality metrics and visual alerts when the connection degrades.
What's Next
Learn about WebSocket error handling
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro