100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Web Development

Rate Limiting WebSocket Connections

Strategies for throttling connection attempts and message throughput to protect WebSocket servers from abuse and resource exhaustion.

SecurityIntermediate8 min readJul 10, 2026
Analogies

Why WebSockets Need Rate Limiting

Unlike a stateless HTTP request that completes in milliseconds, a WebSocket connection is long-lived and holds server resources — a file descriptor, memory buffers, and often a dedicated event-loop slot — for as long as it stays open. A single malicious or buggy client can open thousands of connections, or send messages far faster than the server can process, exhausting file descriptors, memory, or CPU before normal HTTP-based rate limiting middleware (which typically only counts requests, not open connections) even notices a problem. Rate limiting for WebSockets therefore has to operate at two distinct layers: how many connections a client can open, and how fast a client can send messages once connected.

🏏

Cricket analogy: A single spectator holding a season pass and then queue-jumping through every turnstile at once to occupy 500 seats is like one client opening thousands of WebSocket connections; a stadium needs a per-person seat limit, not just a general entry-rate check at the gate.

Connection-Level Limits

The first layer of defense caps how many connections a single client can hold open, typically enforced by IP address at a reverse proxy (nginx's limit_conn module, for instance) and, for authenticated apps, also by user or account ID at the application layer, since a botnet can spread across many IPs but still be tied to one compromised account. Handshake attempts themselves should also be rate-limited (limit_req in nginx, or a token bucket in the app server) since even rejected handshakes consume CPU cycles for TLS negotiation and header parsing, making a flood of connection attempts a viable denial-of-service vector even if every single one is eventually rejected.

🏏

Cricket analogy: A stadium enforcing 'four tickets maximum per ID' at the box office, regardless of how many separate queues someone joins, mirrors capping WebSocket connections per user account rather than only per IP, since one person could queue at multiple gates.

Message-Level Throttling

Once a connection is open, the server still needs to bound how many messages per second it will process from that connection, typically with a per-connection token bucket: each connection accrues tokens at a fixed rate (say, 10 per second) up to a small burst cap, and each incoming message consumes one token; messages arriving with no tokens available are either dropped, queued briefly, or trigger a connection close with a policy-violation close code (1008) if the client repeatedly exceeds the limit. This protects downstream systems — databases, message brokers, other connected clients in a broadcast — from being overwhelmed by a single misbehaving or compromised connection, independent of how many connections that client was allowed to open in the first place.

🏏

Cricket analogy: A bowler is limited to six legal deliveries per over regardless of how fast they can physically bowl, exactly like a token bucket capping messages per second per connection regardless of how fast the client could technically send them.

Backpressure and Slow Clients

Rate limiting incoming messages only solves half the problem — a slow or malicious client can also fail to read messages the server sends it, causing the server's outbound buffer for that connection to grow unbounded. Libraries like ws expose a bufferedAmount property that reports how many bytes are queued but not yet flushed to the socket; a well-behaved server checks this before writing more data and either pauses sends, drops non-critical messages, or forcibly closes the connection once bufferedAmount crosses a threshold (e.g., a few megabytes), preventing one slow consumer from ballooning server memory.

🏏

Cricket analogy: A stadium's PA announcer pausing new announcements if the queue of unbroadcast messages backs up, rather than endlessly stacking them, mirrors checking bufferedAmount before pushing more data to a slow WebSocket client.

javascript
class TokenBucket {
  constructor(ratePerSec, burst) {
    this.tokens = burst;
    this.burst = burst;
    this.ratePerSec = ratePerSec;
    this.last = Date.now();
  }
  tryConsume() {
    const now = Date.now();
    const elapsed = (now - this.last) / 1000;
    this.tokens = Math.min(this.burst, this.tokens + elapsed * this.ratePerSec);
    this.last = now;
    if (this.tokens >= 1) {
      this.tokens -= 1;
      return true;
    }
    return false;
  }
}

wss.on('connection', (ws) => {
  ws.bucket = new TokenBucket(10, 20); // 10 msgs/sec, burst of 20

  ws.on('message', (data) => {
    if (!ws.bucket.tryConsume()) {
      ws.close(1008, 'Rate limit exceeded');
      return;
    }
    handleMessage(ws, data);
  });
});

function safeSend(ws, payload) {
  if (ws.bufferedAmount > 5 * 1024 * 1024) {
    ws.close(1008, 'Backpressure limit exceeded');
    return;
  }
  ws.send(payload);
}

nginx's limit_conn and limit_req modules can enforce both connection-count and handshake-rate limits at the proxy layer before traffic even reaches your application, giving you a cheap first line of defense that doesn't consume application CPU.

Skipping rate limiting entirely because 'our users are trusted' is a common mistake — a single compromised account, buggy client update, or misconfigured retry loop can flood your WebSocket infrastructure just as effectively as a deliberate attacker.

  • WebSocket connections are long-lived and hold resources (file descriptors, memory, event-loop slots) for their entire duration, unlike stateless HTTP requests.
  • Connection-level limits should be enforced both by IP (at a reverse proxy) and by authenticated user/account ID at the application layer.
  • Handshake attempts themselves should be rate-limited, since even rejected handshakes consume CPU for TLS and header parsing.
  • A per-connection token bucket bounds how many messages per second a client can send, protecting downstream systems from floods.
  • bufferedAmount should be checked before sending more data, closing or throttling connections whose outbound buffer grows too large.
  • Rate limiting should never be skipped just because users are 'trusted' — bugs and compromised accounts can cause the same load as an attack.

Practice what you learned

Was this page helpful?

Topics covered

#WebDevelopment#WebSocketsStudyNotes#RateLimitingWebSocketConnections#Rate#Limiting#WebSocket#Connections#StudyNotes#SkillVeris#ExamPrep