Skip to content

Websocket Heartbeats

DodaTech 5 min read

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

  1. What is the purpose of WebSocket ping/pong frames?
  2. How do heartbeats prevent proxy timeout issues?
  3. What is a good heartbeat interval for most applications?
  4. How do you detect a dead WebSocket connection?
  5. 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

What is the difference between ping/pong and TCP keepalive?

Ping/pong are WebSocket protocol frames. TCP keepalive operates at a lower level and has much longer default intervals (2 hours).

Can I send application data in ping/pong frames?

Ping/pong frames cannot carry application data. Use custom heartbeat messages for application-level keepalive with metadata.

What happens if a ping frame is lost?

The sender should not assume the connection is dead after one missed pong. Wait for multiple missed pings before terminating.

Do all WebSocket libraries support ping/pong?

Most libraries support ping/pong. The ws library for Node.js has built-in ping/pong. Browser WebSocket API does not expose ping/pong events.

How do I handle heartbeats in browsers?

Browsers handle ping/pong automatically. You can implement application-level heartbeats by sending periodic custom messages.

Should I use heartbeats on local network connections?

Locally, disconnections are rare. Use longer heartbeat intervals (60s) or disable heartbeats for local development.

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