Scaling Socket.io with Redis Adapter Multi-Server WebSocket Support
Learn how to scale Socket.io across multiple Node.js servers using the Redis adapter ensuring messages reach all connected clients regardless of which server they're on.
Scaling Socket.io with Redis Adapter
When you run multiple Node.js instances (for scaling), Socket.io needs a way to broadcast messages across all instances. The Redis adapter solves this.
The Problem
Instance 1: User A, User B
Instance 2: User C, User D
User A sends a message to "room-1"
Room-1 has User A, B, C
Without Redis adapter:
- Instance 1 broadcasts to User A and B ✓
- Instance 2 doesn't know about the message ✗
- User C never receives it ✗
The Solution: Redis Adapter
npm install @socket.io/redis-adapter redis
const { createAdapter } = require("@socket.io/redis-adapter"); const { createClient } = require("redis"); // Create Redis clients const pubClient = createClient({ url: process.env.REDIS_URL }); const subClient = pubClient.duplicate(); await Promise.all([pubClient.connect(), subClient.connect()]); // Apply adapter io.adapter(createAdapter(pubClient, subClient));
How It Works
User A (Instance 1) sends message to room-1
↓
Instance 1 publishes to Redis: "room-1: message"
↓
Redis broadcasts to all subscribers
↓
Instance 1 receives → delivers to User A, B
Instance 2 receives → delivers to User C
↓
All users in room-1 receive the message ✓
Full Setup
// src/server.js const http = require("http"); const express = require("express"); const socketio = require("socket.io"); const { createAdapter } = require("@socket.io/redis-adapter"); const { createClient } = require("redis"); const app = require("./app"); const server = http.createServer(app); const io = socketio(server, { cors: { origin: process.env.CLIENT_URL, credentials: true } }); // Redis adapter for scaling const initAdapter = async () => { const pubClient = createClient({ url: process.env.REDIS_URL }); const subClient = pubClient.duplicate(); pubClient.on("error", (err) => console.error("Redis pub error:", err)); subClient.on("error", (err) => console.error("Redis sub error:", err)); await Promise.all([pubClient.connect(), subClient.connect()]); io.adapter(createAdapter(pubClient, subClient)); console.log("Socket.io Redis adapter connected"); }; initAdapter(); // Socket events require("./config/socketEvents")(io); server.listen(process.env.PORT || 3000);
Using a Sticky Load Balancer
When using Nginx as a load balancer with multiple Node.js instances, WebSocket connections need sticky sessions (a client always connects to the same instance):
upstream nodejs_backend { ip_hash; # Sticky sessions based on IP server 127.0.0.1:3000; server 127.0.0.1:3001; server 127.0.0.1:3002; } server { location / { proxy_pass http://nodejs_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
ip_hash ensures the same client always connects to the same instance. This is important for WebSocket connections.
Testing the Adapter
# Start 3 instances on different ports PORT=3000 pm2 start src/server.js --name api-1 PORT=3001 pm2 start src/server.js --name api-2 PORT=3002 pm2 start src/server.js --name api-3 # Connect clients to different instances via Nginx # Send a message all clients should receive it
Redis Adapter with PM2 Cluster
// ecosystem.config.js module.exports = { apps: [{ name: "devtinder-api", script: "src/server.js", instances: "max", // One per CPU core exec_mode: "cluster", env: { REDIS_URL: "redis://localhost:6379" } }] };
With the Redis adapter, messages broadcast in one PM2 cluster worker are delivered to clients connected to all workers.
The Takeaway
Scaling Socket.io across multiple Node.js instances requires the Redis adapter (@socket.io/redis-adapter). It uses Redis pub/sub to broadcast messages across all instances. Set up Redis clients, apply the adapter with io.adapter(createAdapter(pubClient, subClient)), and use ip_hash in Nginx for sticky WebSocket sessions. With the adapter, messages reach all clients regardless of which instance they're connected to.
When running multiple Node.js instances, each only knows about its own connected clients. Without the Redis adapter, a message broadcast on one instance won't reach clients on other instances. The Redis adapter uses Redis pub/sub to broadcast across all instances.
Install @socket.io/redis-adapter and redis. Create a Redis pub client and a sub client (duplicate). Connect both, then apply io.adapter(createAdapter(pubClient, subClient)). Now messages broadcast on one instance are delivered to clients on all instances.
Yes. Use ip_hash in Nginx upstream to ensure the same client always connects to the same Node.js instance. This is important for WebSocket connections because they're persistent. Without sticky sessions, the initial HTTP upgrade and subsequent WebSocket frames might go to different instances.
When a message is broadcast, the instance publishes it to a Redis channel. All other instances subscribe to that channel and receive the message. Each instance then delivers the message to its own connected clients that are in the target room. This ensures all clients receive the message regardless of which instance they're on.
Yes. Set exec_mode: 'cluster' and instances: 'max' in the PM2 ecosystem file. With the Redis adapter, messages broadcast in one PM2 cluster worker are delivered to clients connected to all workers. Set REDIS_URL in the env config.
Ready to master Node.js completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master Node.js
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

