Scale Socket.IO Using Redis Adapter
Understand why the default adapter is not suitable for cross-instance broadcasting, integrate Redis Pub/Sub, and prepare Socket.IO for horizontal scaling.
Why Do We Need Socket.IO Scaling?
So far, our application has used a single Node.js server instance.
All connected sockets are maintained within that server process.
Consider what happens when you deploy two Node.js instances behind a load balancer.
- User A connects to Server 1.
- User B connects to Server 2.
- User A sends a message.
- Server 1 broadcasts the message using its local Socket.IO instance.
Without a shared adapter, Server 2 does not automatically receive the broadcast.
This happens because the default Socket.IO adapter stores room and socket communication state locally within each server process.
To solve cross-instance communication, we need an adapter that distributes broadcasts between server instances.
The default adapter is local to a server instance.
Multiple server instances do not automatically share broadcasts.
A distributed adapter enables cross-instance event communication.
Understand the Redis Adapter
The Socket.IO Redis adapter uses Redis Pub/Sub to distribute events between multiple Socket.IO server instances.
Each server connects to Redis using two clients:
- Publisher client: Publishes events to Redis channels.
- Subscriber client: Receives events published by other server instances.
When one server broadcasts an event to a room, the Redis adapter distributes the broadcast to other Socket.IO servers.
Those servers then deliver the event to their locally connected clients.
This allows users connected to different application instances to communicate through the same logical Socket.IO rooms.
Redis Adapter Components
Sends messages
Publishes Socket.IO adapter messages to Redis.
Receives messages
Subscribes to Redis Pub/Sub channels.
Message distribution
Distributes adapter messages between application instances.
Cross-instance broadcasts
Connects Socket.IO broadcasting behavior to Redis Pub/Sub.
Redis Pub/Sub distributes events between server instances.
Publisher and subscriber clients perform different roles.
The Redis adapter extends broadcasting across instances.
Install Redis Adapter Dependencies
Install the Socket.IO Redis adapter and the official Redis Node.js client.
We will use the redis package to connect to Redis.
The adapter package integrates Socket.IO with Redis Pub/Sub.
npm install @socket.io/redis-adapter redisInstalls the official Socket.IO Redis adapter and the Redis Node.js client.
The Redis adapter is a separate package.
The Redis package provides the Node.js client.
Both dependencies are required for this integration.
Start a Redis Server
Before connecting Node.js to Redis, ensure that a Redis server is running.
You can use a local Redis installation or a managed Redis service.
For local development, Docker provides a convenient way to start Redis.
Run the following command:
docker run -d --name socket-chat-redis -p 6379:6379 redis:7-alpine
Verify that the container is running:
docker ps
Test the Redis connection:
docker exec -it socket-chat-redis redis-cli ping
Expected response:
PONG
The example uses a local development container without authentication. Never expose this Redis instance publicly.
This command is intended only for local development. Production Redis should use authentication, TLS where appropriate, private networking, and firewall restrictions.
docker run -d --name socket-chat-redis -p 6379:6379 redis:7-alpineStarts Redis in a Docker container and maps port 6379 to the host machine.
Redis must be running before the adapter connects.
Docker simplifies local Redis setup.
Never expose an unsecured Redis instance to untrusted networks.
Configure Redis Publisher and Subscriber
Create src/config/redis.js.
We need two Redis connections because the adapter uses Pub/Sub.
The publisher client sends messages, while the subscriber client listens for messages.
The subscriber is created by duplicating the publisher client configuration.
import { createClient } from 'redis';
const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';
export const pubClient = createClient({
url: redisUrl
});
export const subClient = pubClient.duplicate();
pubClient.on('error', (error) => {
console.error('Redis publisher error:', error);
});
subClient.on('error', (error) => {
console.error('Redis subscriber error:', error);
});
export async function connectRedis() {
await Promise.all([
pubClient.connect(),
subClient.connect()
]);
console.log('Redis publisher and subscriber connected');
}Creates separate publisher and subscriber clients, registers error listeners, and exposes a connection function.
The Redis adapter requires publisher and subscriber connections.
The subscriber can be created using duplicate().
Wait for Redis connections before starting the application.
Attach Redis Adapter to Socket.IO
Update src/config/socket.js to initialize the Redis adapter.
The createAdapter() function receives the publisher and subscriber clients.
The resulting adapter is configured through the Socket.IO server's adapter option.
import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { pubClient, subClient } from './redis.js';
export function initializeSocket(httpServer) {
const io = new Server(httpServer, {
adapter: createAdapter(pubClient, subClient)
});
return io;
}Configures Socket.IO to use Redis Pub/Sub for cross-instance broadcasting instead of relying only on its default in-memory adapter.
The adapter is configured during Socket.IO initialization.
Redis publisher and subscriber clients are shared with the adapter.
All instances must use compatible adapter configuration.
Start the Application After Redis Connects
The application must not start accepting connections before Redis is ready when the Redis adapter is required for correct cross-instance broadcasting.
Update src/server.js to connect Redis before starting the HTTP server.
This prevents clients from connecting to an instance that has not completed adapter initialization.
If Redis is a required dependency for cross-instance messaging, do not silently start in-memory mode when Redis is unavailable. Either fail startup or implement an explicitly designed degraded mode.
import 'dotenv/config';
import express from 'express';
import { createServer } from 'node:http';
import { initializeSocket } from './config/socket.js';
import { connectRedis } from './config/redis.js';
import { registerChatEvents } from './events/chat.events.js';
const app = express();
app.use(express.json());
app.use(express.static('public'));
const httpServer = createServer(app);
async function startServer() {
await connectRedis();
const io = initializeSocket(httpServer);
registerChatEvents(io);
const PORT = process.env.PORT || 3000;
httpServer.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
}
startServer().catch((error) => {
console.error('Application startup failed:', error);
process.exit(1);
});Connects Redis first, initializes Socket.IO with the Redis adapter, registers events, and finally starts the HTTP server.
Initialize Redis before Socket.IO.
Register event handlers before accepting connections.
Use fail-fast startup when distributed messaging is required.
Understand Redis Adapter Limitations
The Redis adapter enables cross-instance broadcasting, but it does not solve every scaling problem.
Important considerations:
1. Sticky sessions
When HTTP long-polling is enabled and multiple Socket.IO servers are used behind a load balancer, sticky sessions are still required so requests belonging to one Engine.IO session reach the correct server.
2. Connection state recovery
The standard Redis adapter does not support Socket.IO connection state recovery. Do not assume that missed packets can always be restored after disconnection.
3. Redis availability
A Redis outage can affect cross-instance communication.
4. Local socket management
Each server still manages its own local sockets. Redis distributes adapter messages between instances.
5. Redis security
Protect Redis with authentication, ACLs, TLS where appropriate, and network restrictions.
For Redis 7.0 and newer, Socket.IO also provides a sharded adapter designed for Redis sharded Pub/Sub.
The Redis adapter handles inter-server event distribution. It does not remove the need for sticky sessions when using HTTP long-polling across multiple Socket.IO servers.
Socket.IO Scaling Options
| Feature | Default Adapter | Redis Adapter | Redis Sharded Adapter |
|---|---|---|---|
| Single instance | Yes | Yes | Yes |
| Cross-instance broadcasts | No | Yes | Yes |
| Redis required | No | Yes | Yes |
| Redis 7 sharded Pub/Sub | No | No | Yes |
| Connection state recovery | Supported | Not supported | Depends on implementation |
Redis adapter distributes events between instances.
Sticky sessions may still be necessary.
The standard Redis adapter does not support connection state recovery.
Redis infrastructure must be secured and monitored.