Scaling SignalR With a Redis Backplane in ASP.NET Core
Learn how a Redis backplane lets your ASP.NET Core SignalR app run on many servers so every connected user still gets every real-time message.
Scaling SignalR With a Redis Backplane in ASP.NET Core
Imagine a busy railway station in India. There are many ticket counters open at once. A passenger walks up to Counter 3 and stands there for the whole transaction. Now the station manager wants to make an announcement that every passenger must hear, no matter which counter they are standing at. If the manager only shouts near Counter 3, the people at Counter 1 and Counter 5 will never hear it.
So the station uses a central public-address system. The manager speaks once into a microphone, and every speaker at every counter plays the message. Everyone hears it, no matter where they stand.
A Redis backplane for SignalR is exactly that central PA system. Your app runs on many servers (the counters). A user stays connected to one server. When any server wants to send a real-time message to everyone, it speaks into the shared microphone (Redis), and Redis makes sure every server plays the message to its own connected users.
This article explains why you need this, how it works, and how to set it up step by step.
Why one server is not enough
When you build a chat app, a live scoreboard, or a notifications feed with SignalR, it works perfectly on your laptop. One server holds all the connections. When a message arrives, that one server knows about every connected user and sends to all of them.
But real apps get popular. One server cannot hold thousands of live connections forever. So you add more servers behind a load balancer. This is called scaling out.
Here is the problem. Each server only knows about its own connected users.
In the diagram above, Server 1 sends a message to "all users". But "all users" really means "all users I personally know about". User C is connected to Server 2, so User C never gets the message. That is a broken chat app.
We need a way for all servers to share messages. That shared channel is the backplane.
What a backplane actually does
Redis is a fast in-memory store. One of its features is publish/subscribe (often shortened to pub/sub). A server can publish a message to a named channel, and every other server that subscribed to that channel receives a copy.
SignalR uses this pub/sub feature. When you add the Redis backplane:
- Every app server subscribes to a Redis channel.
- When any server wants to broadcast, it publishes the message to Redis.
- Redis sends a copy to every subscribed server.
- Each server then delivers the message to its own connected users.
Now when Server 1 broadcasts, Redis forwards the message to Server 2 and Server 3 too. Every user gets the message, no matter which server they are on. The "User C misses it" problem is gone.
The journey of one broadcast message
Steps
Send
User clicks send; message hits one server
Server
That server publishes to Redis
Redis
Redis fans the message out to all servers
Servers
Each server delivers to its own users
Clients
Everyone sees the message in real time
Step 1: Install the package
The backplane lives in a NuGet package called Microsoft.AspNetCore.SignalR.StackExchangeRedis. It uses the popular StackExchange.Redis client under the hood.
Add it to your project:
// Run this in your project folder (terminal / Package Manager)
// dotnet add package Microsoft.AspNetCore.SignalR.StackExchangeRedis
// The package version usually matches your .NET version.
// On .NET 10, you would use the 10.x version of the package.This single package is all you need on the application side. You also need a running Redis server, which we cover near the end.
Step 2: Wire it up in Program.cs
In a minimal ASP.NET Core app, you register SignalR and then chain the Redis backplane onto it. The key line is AddStackExchangeRedis.
var builder = WebApplication.CreateBuilder(args);
// Add SignalR, then attach the Redis backplane.
builder.Services
.AddSignalR()
.AddStackExchangeRedis(
builder.Configuration.GetConnectionString("Redis")!);
var app = builder.Build();
app.MapHub<ChatHub>("/chat");
app.Run();That is the whole change. Your hub code (ChatHub) does not change at all. You write Clients.All.SendAsync(...) exactly as before. SignalR quietly routes the broadcast through Redis for you.
Your connection string lives in appsettings.json:
// appsettings.json
// {
// "ConnectionStrings": {
// "Redis": "my-redis-host:6379"
// }
// }Step 3: Set a channel prefix (very important)
Here is a small detail that bites many teams. If two different SignalR apps share the same Redis server, and you do not tell them apart, then a broadcast from App A will also reach the users of App B. That is a confusing bug and a possible data leak.
The fix is a channel prefix. It is like writing your app's name on its own private radio frequency so other apps cannot hear you.
using StackExchange.Redis;
builder.Services
.AddSignalR()
.AddStackExchangeRedis(connectionString, options =>
{
// Isolate this app's messages from other apps on the same Redis.
options.Configuration.ChannelPrefix =
RedisChannel.Literal("ChatApp");
});Give each app a different prefix and they will never cross wires.
Step 4: Keep sticky sessions on
This is the part people forget. The Redis backplane solves message routing between servers. It does not remove the need for sticky sessions (also called session affinity).
A SignalR connection (especially with WebSockets) is a long-lived link. The client must keep talking to the same server for the whole life of that connection. If your load balancer sends the next request to a different server, the connection breaks.
So you need two things working together:
| Concern | Who handles it |
|---|---|
| Keep one client on one server | Sticky sessions on the load balancer |
| Share broadcasts across all servers | Redis backplane |
Both are required. The backplane is not a replacement for sticky sessions.
How the pieces fit together
Let us zoom out and look at the full shape of a scaled-out SignalR system. You have clients, a load balancer with sticky sessions, several app servers, and one shared Redis that ties them together.
Scaled-out SignalR architecture
Steps
Clients
Browsers and apps holding live connections
Balancer
Sends each user to a server and keeps them there
Servers
Many app instances, each with some users
Backplane
Redis pub/sub shares every broadcast
Delivery
Each server sends to its own connected users
When Redis is the right choice
The Redis backplane is the recommended scale-out approach for apps you host on your own infrastructure — your own data center, virtual machines, or Kubernetes cluster. It is simple, well-supported, and fast when Redis lives close to your servers.
If your app runs in Azure, Microsoft recommends the Azure SignalR Service instead. It manages all the live connections for you, scales to huge numbers of users, and does not even need sticky sessions because clients connect directly to the service.
Here is a quick comparison to help you choose.
| Feature | Redis backplane | Azure SignalR Service |
|---|---|---|
| Where it shines | Your own servers / on-prem | Apps hosted in Azure |
| Sticky sessions needed | Yes | No |
| Who holds connections | Your app servers | The managed service |
| You run the infra | Yes (Redis) | No (fully managed) |
| Best for | Full control, no cloud lock-in | Easy scaling, less ops work |
A few real-world tips
Run Redis near your app. For production, a Redis backplane is recommended only when Redis runs in the same data center as your app. If Redis is far away, every message pays a network latency cost and your app feels slow.
Plan for Redis going down. When the Redis server is unavailable, SignalR throws errors saying messages cannot be delivered, and any message sent during the outage is lost. Use a highly available Redis setup such as Redis Sentinel or a managed Redis cluster so a single failure does not stop your app.
Test with more than one server. On your laptop you only run one instance, so the backplane bug never shows up. Spin up two instances locally (different ports) behind a small proxy, open two browser tabs on different instances, and confirm a message in one tab reaches the other. That quick test catches most mistakes.
Here is a tiny hub so you can see that your code stays clean — the backplane is invisible from here:
using Microsoft.AspNetCore.SignalR;
public class ChatHub : Hub
{
// This same call now reaches users on every server,
// thanks to the Redis backplane. No special code needed.
public Task SendMessage(string user, string message)
=> Clients.All.SendAsync("ReceiveMessage", user, message);
}Notice how Clients.All.SendAsync(...) did not change. That is the beauty of the backplane: you add a few lines in Program.cs, and the rest of your real-time code keeps working as it always did.
States your connection moves through
It also helps to picture the life of a single client connection. It connects, stays alive while sticky sessions hold it on one server, may briefly reconnect if the network blips, and finally closes.
Throughout this whole lifecycle, the backplane keeps doing its quiet job in the background: sharing every broadcast so that no matter which server the client lands on, it still receives every message meant for it.
Common mistakes and how to avoid them
Most problems with a Redis backplane are not deep bugs. They are small setup slips that are easy to fix once you know to look for them. Here are the ones that trip up beginners most often.
The first mistake is forgetting sticky sessions. The app seems to work for a while, then connections randomly drop or fail to start. This happens because the load balancer keeps moving the client between servers. Turn on session affinity and the random failures disappear.
The second mistake is sharing a Redis server without a channel prefix. Two apps work fine on their own, but once they share Redis, users start seeing messages that were never meant for them. Always set a different ChannelPrefix for each app.
The third mistake is putting Redis far away. The app works but feels laggy, with messages arriving a second or two late. Move Redis into the same data center, ideally the same network, as your app servers.
The fourth mistake is assuming Redis never fails. In a demo, one Redis instance is fine. In production, that single instance is a single point of failure. Use a replicated, highly available Redis so one crash does not take your real-time features down with it.
| Symptom you see | Likely cause | Fix |
|---|---|---|
| Connections drop at random | No sticky sessions | Enable session affinity |
| Users get other apps' messages | Shared Redis, no prefix | Set a unique ChannelPrefix |
| Messages arrive slowly | Redis is far from app | Co-locate Redis with servers |
| Real-time stops during an outage | Single Redis instance | Use a highly available Redis |
If you keep this short checklist nearby while you set things up, you will sidestep almost every common backplane headache before it ever reaches your users.
Quick recap
- One SignalR server only knows about its own users, so broadcasts miss users on other servers when you scale out.
- A Redis backplane uses Redis pub/sub to share every broadcast with all servers, so every user gets every message.
- Install
Microsoft.AspNetCore.SignalR.StackExchangeRedisand callAddStackExchangeRedis(...)afterAddSignalR(). - Set a channel prefix so multiple apps on one Redis server do not mix messages.
- You still need sticky sessions — the backplane shares messages, but each client must stay on one server.
- Keep Redis close to your app, and make it highly available, because messages are lost while Redis is down.
- On your own servers, use the Redis backplane. In Azure, prefer the Azure SignalR Service.
References and further reading
- Redis backplane for ASP.NET Core SignalR scale-out (Microsoft Learn)
- ASP.NET Core SignalR production hosting and scaling (Microsoft Learn)
- Microsoft.AspNetCore.SignalR.StackExchangeRedis on NuGet
- Scaling SignalR With a Redis Backplane (Milan Jovanović)
Related Posts
Adding Real-Time Functionality to .NET Apps with SignalR
Learn ASP.NET Core SignalR step by step: hubs, clients, groups, and scaling with Redis or Azure, explained for absolute beginners.
Real-Time Server-Sent Events in ASP.NET Core and .NET 10
Learn Server-Sent Events (SSE) in ASP.NET Core and .NET 10 with the new TypedResults.ServerSentEvents API, explained simply for beginners.
Advanced Rate Limiting Use Cases in .NET: A Friendly Deep Dive
Go beyond the basics of ASP.NET Core rate limiting: per-user limits, chained limiters, friendly 429 responses, Redis for many servers, and tier-based rules.
Server-Sent Events in ASP.NET Core and .NET 10
Learn Server-Sent Events in ASP.NET Core .NET 10 with TypedResults.ServerSentEvents and IAsyncEnumerable, explained simply for beginners.
Solving Distributed Cache Invalidation with Redis and HybridCache
Learn how Redis and HybridCache solve distributed cache invalidation in ASP.NET Core with tags, backplanes, and a simple kitchen-counter analogy.
How to Add JWT Authentication to SignalR Hubs in ASP.NET Core
A beginner-friendly guide to securing SignalR hubs with JWT tokens in ASP.NET Core, including the access_token query string trick and the [Authorize] attribute.