Introduction
Imagine you have built a real-time chat application using ASP.NET Core SignalR. Everything works well while the application runs on a single server. Users can send messages, receive notifications, and see updates without refreshing the page.
As the number of users grows, you decide to run the application on multiple servers behind a load balancer. You expect this change to handle more connections, but you soon discover a problem: a message sent by a user connected to one server does not always reach users connected to another server.
Why does this happen?
Each SignalR server manages its own client connections. Without a suitable scale-out solution, one server does not automatically know how to deliver messages to clients connected to another server.
This is an important consideration when moving a real-time application into production.
In this article, we will learn how SignalR works across multiple servers, understand the role of Redis and Azure SignalR Service, and configure a scalable ASP.NET Core SignalR application.
Why Does SignalR Need a Scale-Out Solution?
SignalR maintains persistent connections between clients and the server. Depending on the available transport, these connections may use WebSockets, Server-Sent Events, or long polling.
Consider an application running on two servers behind a load balancer.
Clients
|
Load Balancer
/ \
/ \
Server A Server B
User 1 User 2User 1 is connected to Server A, while User 2 is connected to Server B.
When User 1 sends a message, Server A can deliver it to the clients it manages. However, it cannot automatically deliver the message to User 2 unless the application has a way to distribute messages across servers.
This is where SignalR scale-out comes in.
A scale-out solution helps multiple servers coordinate message delivery. It does not necessarily move client connections between servers or share every piece of application state.
What Are the Main Options for Scaling SignalR?
ASP.NET Core SignalR supports different approaches to scale-out. The right option depends on your hosting environment, connection volume, infrastructure, and operational requirements.
Approach | How it works | Best suited for |
|---|---|---|
Single server | One server manages all SignalR connections | Small applications and development |
Redis backplane | Servers distribute SignalR messages through Redis | Self-managed or cloud-hosted server farms |
Azure SignalR Service | A managed service handles client connections and distributes messages | Applications hosted on Azure or environments that can connect to the service |
Running multiple application servers without a scale-out mechanism is not enough to guarantee that messages reach clients connected to other servers.
Option 1: Use Redis as a SignalR Backplane
A Redis backplane allows multiple SignalR servers to exchange messages through a shared Redis deployment.
For example, when a message is published through Server A, the backplane distributes it so that Server B can deliver it to its connected clients.
Clients
|
Load Balancer
/ \
/ \
Server A Server B
\ /
\ /
Redis
BackplaneRedis does not need to manage every client connection itself. The SignalR application servers continue to handle their own connections, while Redis helps distribute messages between them.
Step 1: Install the Redis package
In your ASP.NET Core SignalR project, install the Redis integration package.
dotnet add package Microsoft.AspNetCore.SignalR.StackExchangeRedisUse a package version compatible with your target .NET framework.
Step 2: Configure SignalR with Redis
Add the Redis connection string to your application configuration.
{
"ConnectionStrings": {
"Redis": "localhost:6379"
}
}This example uses a local Redis instance for development. In production, use an appropriately secured Redis deployment and store credentials in a secure configuration provider.
Now configure SignalR in Program.cs.
var builder = WebApplication.CreateBuilder(args);
var redisConnection =
builder.Configuration.GetConnectionString("Redis")
?? throw new InvalidOperationException(
"Redis connection string is missing.");
builder.Services
.AddSignalR()
.AddStackExchangeRedis(redisConnection);
var app = builder.Build();
app.MapHub<ChatHub>("/chatHub");
app.Run();The important part is AddStackExchangeRedis(). It configures Redis as the SignalR backplane so that messages can be distributed across application servers using the same Redis deployment.
Each server must use compatible SignalR configuration and connect to the appropriate shared backplane.
Step 3: Create a SignalR hub
Create a simple hub that allows clients to send messages.
using Microsoft.AspNetCore.SignalR;
public class ChatHub : Hub
{
public async Task SendMessage(
string user,
string message)
{
await Clients.All.SendAsync(
"ReceiveMessage",
user,
message);
}
}The Clients.All expression targets all connected clients known to the SignalR application. With a correctly configured Redis backplane, the message can reach clients connected to different application servers.
In a production application, derive user identity from authenticated claims instead of trusting a user name supplied by the client.
An important point about sticky sessions
Using Redis does not automatically remove the need for sticky sessions.
In a server farm, SignalR generally requires a client to remain connected to the server that handles its connection. This is known as session affinity or sticky sessions.
There are documented exceptions: sticky sessions are not required when the application uses Azure SignalR Service, runs on a single server, or uses WebSockets exclusively with negotiation skipped.
For the WebSockets exception, the client and server configuration must actually satisfy those conditions. Simply enabling WebSockets is not enough if negotiation is still used.
Redis primarily distributes messages between servers; it is not a replacement for connection affinity.
Option 2: Use Azure SignalR Service
If your application runs in Azure, Azure SignalR Service is another option. It is a managed service that handles client connections and distributes messages between the application and connected clients.
Instead of making every application server manage all client connections directly, the service takes on much of the connection-management work.
Join the conversation! Your thoughts help the community grow.