Socket.IO rooms don't cross server nodes—use the Redis adapter when you scale
Socket.IO rooms are local to one server process. Learn when to add the Redis adapter for multi-node broadcasts.
11 Sept 2026, 02:50 UTC

Rooms stay on the server that created them
A Socket.IO room is a named group tracked in a single server instance’s memory. When a client calls socket.join('order-42'), the server adds that connection to its local room map. A later socket.to('order-42').emit('status-update', payload) reaches only connections known to that same process.
This is fine on one server. It breaks behind a load balancer.
One node versus many nodes
If you run two Socket.IO processes and a client on each joins the same room, each process believes it is the only member. A broadcast from node one never reaches node two.
| Setup | Cross-node room broadcast | Operational cost |
|---|---|---|
| Single Socket.IO process | Works; all room members are local | Low |
| Multiple processes, no adapter | Fails; members on other nodes are invisible | Low, but feature is broken |
| Multiple processes with Redis adapter | Works; Redis forwards the payload between nodes | Requires Redis infrastructure |
Worked example: two nodes, one room
Run this on each server node. It assumes Socket.IO v4 and a Redis instance reachable from both nodes.
const { Server } = require('socket.io');
const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
await pubClient.connect();
await subClient.connect();
const io = new Server(httpServer, {
cors: { origin: 'https://example.com' },
});
io.adapter(createAdapter(pubClient, subClient));
io.on('connection', (socket) => {
socket.join('order-42');
socket.on('status-update', (data) => {
socket.to('order-42').emit('status-update', data);
});
});
- Where to run: a terminal on each server node, as a user with permission to bind the application port and read REDIS_URL.
- Placeholders: replace REDIS_URL with your Redis connection string and httpServer with your Node HTTP server instance.
- Expected check: connect one client to node one and another to node two. Have both join order-42. Emit status-update from node one; the client on node two should receive the same payload.
- Risk: if Redis is unreachable, cross-node broadcasts can fail or slow down. The adapter package name changed between Socket.IO v3 (socket.io-redis) and v4 (@socket.io/redis-adapter), so match the package to your installed version.
How to verify the result
- Open two browser clients, each routed to a different Socket.IO node by your load balancer.
- Have both clients join room order-42.
- Trigger an emit from node one.
- Confirm the node-two client receives the event. On the server, inspect socket.rooms after a join to confirm local membership; it will not show members on other nodes, which is expected.
Trade-offs and limitations
- The Redis adapter is only necessary for horizontal scaling. A single server instance does not need it, and adding Redis introduces another service to monitor.
- Socket.IO acknowledgments are application callbacks, not guaranteed delivery. They do not retry or survive a process restart.
- Room membership is lost when a socket disconnects. Socket.IO’s automatic reconnection does not re-add the client to rooms; your client code should rejoin after reconnecting.
- Socket.IO may fall back to HTTP long-polling when WebSocket is blocked, which increases latency and connection cost compared with a WebSocket upgrade.
Closing decision
Start with rooms on a single Socket.IO process. Add the Redis adapter only when you run multiple processes behind a load balancer and need room broadcasts to reach every node. Verify with two clients on different nodes before treating the feature as complete.