Senior 7 min · March 06, 2026

SignalR — Missing Redis Password Causes Silent Fallback

Connected users saw no errors; messages disappeared across servers.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • SignalR is ASP.NET Core's real-time library enabling server-to-client push via WebSockets with automatic fallback.
  • Hub pattern: server-side methods called from clients; client-side methods called from server.
  • Transport negotiator picks WebSocket first, then Server-Sent Events, then Long Polling.
  • MessagePack serialization cuts payload size ~40% over JSON with minimal CPU cost.
  • Production trap: without a backplane (Redis/Azure SignalR), messages only reach clients on the same server instance.
  • Biggest mistake: assuming WebSockets always work — fallback to Long Polling spikes server connections and latency.
Plain-English First

Imagine a pizza tracker on a delivery app. When your pizza leaves the oven, the screen updates instantly — you didn't refresh the page, the server just told your browser. That's SignalR. It keeps an open conversation channel between your browser and server so the server can shout updates at you the moment something happens, instead of waiting for you to ask. It's the difference between a friend who texts you when your table is ready versus you having to call the restaurant every two minutes.

Most web apps are request-response. Client asks, server answers, connection dies. That works for blog posts. It falls apart for live stock tickers or chat. Polling is the duct-tape fix developers reach for first — wasted CPU, inflated bandwidth, and still a perceptible lag. Real-time user experiences demand a fundamentally different communication model. SignalR gives you a persistent channel between server and client, with automatic fallback when WebSockets aren't available. It's not raw sockets — it's a Hub abstraction that handles serialization, invocation routing, and connection lifecycle so you can focus on business logic. But that abstraction hides sharp edges that will cut you in production.

Here's the thing: every connection costs you memory. A single SignalR connection can easily consume 10 KB of server-side memory. Scale to 10,000 concurrent users, and you're looking at 100 MB just for the socket overhead. That's before you add your business logic state. The trade-off is clear — you get sub-100ms message delivery instead of the 5-second polling interval, but you must plan for resource growth.

What is SignalR for Real-time Apps?

At its simplest, SignalR is a library that lets your server broadcast messages to connected clients instantly. The client doesn't poll — it just listens. Under the hood, it wraps WebSockets when available and degrades gracefully. Think of it as a persistent pipe from server to client. The pipe isn't free — each open connection consumes memory on the server and network resources. But for anything that needs live updates, it's orders of magnitude more efficient than polling.

What makes SignalR different from raw WebSockets is the Hub abstraction. You define methods on the server that clients can call, and the server can call methods on the client. SignalR handles serialization, invocation routing, and connection management. You focus on the business logic, not the socket plumbing.

The Hub pattern also provides lifecycle hooks: OnConnectedAsync and OnDisconnectedAsync. Use them to track connections, assign users to groups, or log connection durations. But be careful — a hub instance is created per method invocation, not per connection. State that needs to survive across calls belongs in Context.Items, not in fields.

A common real-time scenario: stock price feed. Your server receives price updates from an external source and pushes them to all clients subscribed to that symbol. Without SignalR, you'd poll the server every second. With SignalR, the server pushes the new price the moment it arrives. The client updates the UI in under 50ms. That's the difference between a usable trading app and one that loses customers because prices are stale.

ChatHub.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using Microsoft.AspNetCore.SignalR;
using Io.TheCodeForge.SignalR.Hubs;

namespace Io.TheCodeForge.Chat
{
    public class ChatHub : Hub
    {
        public async Task SendMessage(string user, string message)
        {
            // Broadcast to all connected clients
            await Clients.All.SendAsync("ReceiveMessage", user, message);
        }

        public override async Task OnConnectedAsync()
        {
            // Track connection start time
            Context.Items["ConnectedAt"] = DateTime.UtcNow;
            Console.WriteLine($"Client connected: {Context.ConnectionId}");
            await base.OnConnectedAsync();
        }

        public override async Task OnDisconnectedAsync(Exception? exception)
        {
            // Log connection duration
            var connectedAt = (DateTime)Context.Items["ConnectedAt"];
            var elapsed = DateTime.UtcNow - connectedAt;
            Console.WriteLine($"Client {Context.ConnectionId} disconnected after {elapsed.TotalSeconds}s");
            await base.OnDisconnectedAsync(exception);
        }
    }
}
Forge Tip:
Type this code yourself rather than copy-pasting. The muscle memory of writing it will help it stick.
Production Insight
A team once used SignalR for a real-time dashboard without understanding transport negotiation. When WebSockets failed under load, Long Polling kicked in and doubled server CPU. The dashboard became unusable during peak hours.
Rule: Always test under load with the expected transport mix.
Key Takeaway
SignalR solves the server-push problem efficiently.
But each transport has different resource costs.
Know which transport your clients use in production.

Transport Negotiation: How SignalR Picks the Right Pipe

SignalR doesn't assume every client supports WebSockets. When a connection starts, the client sends a negotiate request listing the transports it supports. The server picks the best one available: WebSocket > Server-Sent Events > Long Polling. That fallback chain is what makes SignalR work in corporate proxies or restrictive networks.

You can override the transport with the WithUrl method on the client. Forcing WebSocket-only will cause connection failures on mobile devices or behind older proxies. Always let SignalR negotiate unless you have explicit constraints.

To see which transport a client is using, enable debug logging: Logging:LogLevel:Microsoft.AspNetCore.SignalR=Debug. You'll see lines like "Transport 'WebSockets' selected" or "Falling back to LongPolling".

Also note: the negotiate request itself is an HTTP call. If your load balancer doesn't handle that properly (e.g., strips headers, applies SSL termination incorrectly), the negotiation can fail before WebSocket upgrade even starts.

A real example: a client behind a strict corporate proxy saw all connections fall back to Long Polling. The proxy was stripping the Upgrade header. The fix was to configure the proxy to allow WebSocket connections for the specific subdomain. After that, 100% of clients used WebSockets and server CPU dropped by 60%.

SignalRTransportConfig.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using Microsoft.AspNetCore.SignalR.Client;

namespace Io.TheCodeForge.SignalR
{
    public class TransportConfigExample
    {
        public async Task ConnectWithAllTransports()
        {
            var connection = new HubConnectionBuilder()
                .WithUrl("https://api.myapp.com/chat")
                .Build();

            // On client start, SignalR negotiates automatically.
            await connection.StartAsync();
        }

        public async Task ForceWebSocketOnly()
        {
            var connection = new HubConnectionBuilder()
                .WithUrl("https://api.myapp.com/chat", options =>
                {
                    options.SkipNegotiation = true;
                    options.Transports = HttpTransportType.WebSockets;
                })
                .Build();

            await connection.StartAsync();
        }
    }
}
Transport Fallback Cost
Long Polling can increase server load by keeping many HTTP connections open and polling frequently. In production, monitor the fraction of clients on non-WebSocket transports. If >5%, investigate network restrictions or upgrade proxy configurations.
Production Insight
In one deployment behind a corporate proxy, 30% of clients fell back to Long Polling because the proxy stripped the WebSocket Upgrade header.
The server's active connection count doubled, and message latency increased by 200ms.
Rule: Always test from the actual network environment early — don't assume WebSockets work everywhere.
Key Takeaway
SignalR negotiates WebSocket → SSE → Long Polling.
Skip negotiation only when you have full control over the network.
The fallback is not free — Long Polling costs CPU and connections.

Hub Internals and Message Serialization

Hubs are the central class that receive method calls from clients and invoke client-side methods. Each hub method runs asynchronously. The hub's lifetime is per-client-invocation — you cannot store state in hub fields across calls. Use Context.Items for per-connection state.

SignalR serializes messages using JSON by default. Switching to MessagePack can reduce payload size by ~40% and improve throughput because binary serialization is faster to parse. Enable it on both client and server.

Example of enabling MessagePack on server: services.AddSignalR().AddMessagePackProtocol(); On client: .AddMessagePackProtocol() in the HubConnectionBuilder.

Another important internal: the hub dispatcher uses reflection to find methods. If you overload a method name, only one is chosen – avoid overloaded hub methods. Also, method parameters are deserialized from the message; if they don't match, you get a silent invocation skip.

Hub methods must return Task (or Task<T>). async void will cause unobserved exceptions that crash the SignalR pipeline. Always return Task, even if the method is void-like.

Performance tip: if you're sending large payloads frequently, consider compressing the data before sending. SignalR doesn't compress message bodies out of the box. You can implement a custom HubFilter that compresses outgoing messages and decompresses incoming ones. This can reduce bandwidth by 80% for large JSON payloads, but adds CPU overhead.

ChatHub.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using Io.TheCodeForge.SignalR.Hubs;
using Microsoft.AspNetCore.SignalR;

namespace Io.TheCodeForge.Chat
{
    public class ChatHub : Hub
    {
        public async Task SendMessage(string user, string message)
        {
            // Broadcast to all connected clients
            await Clients.All.SendAsync("ReceiveMessage", user, message);
        }

        public override async Task OnConnectedAsync()
        {
            // Track connection duration via Context.Items
            Context.Items["ConnectedAt"] = DateTime.UtcNow;
            await base.OnConnectedAsync();
        }

        public override async Task OnDisconnectedAsync(Exception? exception)
        {
            var elapsed = DateTime.UtcNow - (DateTime)Context.Items["ConnectedAt"];
            // Log connection duration for monitoring
            await base.OnDisconnectedAsync(exception);
        }
    }
}
Hub instance: scoped like a service, not a singleton
  • A new hub instance is created per-client invocation, not per connection.
  • You cannot store state in fields across multiple calls — use Context.Items for per-connection data.
  • Hub methods must return Task or void (async void will crash the SignalR pipeline).
  • Dependency injection is supported via constructor injection for services needed in every call.
Production Insight
Once, an engineer stored a large object in a hub field, expecting it to persist across calls. It didn't — every method invocation created a new instance, and the object was re-initialized, causing a subtle bug where state was always fresh.
The fix: use Context.Items with a string key (like "ConnectedAt") which survives across calls within the same connection.
Rule: never assume hub fields persist — use Context.Items for the lifetime of the connection.
Key Takeaway
Hubs are transient per-invocation, not per-connection.
State that spans calls belongs in Context.Items.
For persistent state (user profile), inject a scoped service that loads and caches data.

Connection Lifecycle: Groups, Reconnection, and State

SignalR groups allow you to send messages to a subset of connections. Groups are server-side only and exist as long as at least one connection is in the group. They are tied to the server instance — a group created on server A cannot be used from server B unless you use a backplane.

Reconnection is built-in: the client automatically retries after disconnection. The WithAutomaticReconnect method lets you configure retry intervals and behaviors. However, any state in Context.Items is lost on reconnect because it's a completely new connection.

When a client reconnects, it gets a brand new ConnectionId, and all prior group memberships are gone. Your code must handle re-joining groups. Typically you save the list of groups the user should be in on the client side (localStorage) and re-join them after reconnect. On the server, OnConnectedAsync is the right place to validate and re-add group memberships based on authentication claims.

Another edge: if you use Groups.AddToGroupAsync and then the connection drops before the group add is acknowledged, the add may not have taken effect. Use ack-based patterns if group membership is critical.

In a multi-server setup without a backplane, groups are per-server — a group on server A has no meaning on server B. With a backplane, groups become global.

One more thing: the maximum number of groups per connection is unlimited, but listing all groups for a connection is not possible via the public API. If you need this, maintain your own mapping in a concurrent dictionary or Redis.

GroupOperations.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using Io.TheCodeForge.SignalR.Hubs;
using Microsoft.AspNetCore.SignalR;

namespace Io.TheCodeForge.Chat
{
    public class RoomHub : Hub
    {
        public async Task JoinRoom(string roomName)
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
            await Clients.OthersInGroup(roomName).SendAsync("UserJoined", Context.ConnectionId);
        }

        public async Task LeaveRoom(string roomName)
        {
            await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
        }

        public async Task SendToRoom(string roomName, string message)
        {
            await Clients.Group(roomName).SendAsync("ReceiveRoomMessage", message);
        }

        public override async Task OnConnectedAsync()
        {
            // After reconnect, the client will re-join rooms via a separate endpoint
            await base.OnConnectedAsync();
        }
    }
}
Reconnection and State Loss
When a client reconnects, it gets a brand new ConnectionId. Any group memberships from the old connection are gone. You must re-add the client to groups after reconnect, typically in OnConnectedAsync or from the client rejoining rooms.
Production Insight
A trading platform used groups to send price updates to subscribed users. After a server restart, all groups were empty, and users stopped receiving updates until they refreshed the page.
The fix: on reconnect, the client re-joins its cached set of groups (stored in localStorage). The server also re-accepts the join request in OnConnectedAsync based on user claims.
Rule: groups are ephemeral — design for reconnection by persisting group state on the client or in a database.
Key Takeaway
SignalR groups are server-local and ephemeral.
Reconnection clears groups; handle re-join in OnConnectedAsync.
For cross-server groups, a backplane is mandatory.
Choosing a Group Strategy
IfSmall number of rooms (<100), low churn
UseUse SignalR groups directly — simple and efficient.
IfMany rooms (>1000) with frequent join/leave
UseConsider managing membership in a Redis set and routing via custom middleware; groups scale but list operations are O(n).
IfMessages need to target users across servers (no backplane)
UseUse Azure SignalR Service or Redis backplane — groups are automatically distributed.

Scaling SignalR with a Backplane: Redis and Azure SignalR Service

Out of the box, SignalR sends messages only to clients connected to the same server instance. To scale out across multiple servers, you need a backplane — a component that relays messages between servers. The two most common options are Redis backplane and Azure SignalR Service.

Redis backplane uses pub/sub channels; each server subscribes to all channels and publishes messages for other servers to receive. Azure SignalR Service acts as a managed proxy — your servers push messages to the service, and the service delivers them directly to clients, removing the need for sticky sessions.

When using Redis, pay attention to connection string syntax. The format is: redis://:password@host:port. If you omit the colon before the password, Redis will try to authenticate with an empty password. SignalR logs a single INFO-level warning on failure, which is easy to miss. Always verify the backplane is working by checking startup logs or using a Redis monitor.

Azure SignalR Service handles scaling more gracefully — you pay per unit. But it introduces additional latency (the message goes from server to Azure to client). For most apps this is negligible.

Important: if you use Redis backplane, you don't need sticky sessions because messages are relayed. However, if you don't use a backplane, you MUST use sticky sessions to ensure a client's subsequent requests hit the same server where their connection lives.

A hidden gotcha: backplane message ordering is best-effort in Redis. If your app requires strict per-session message order, you need a deterministic routing strategy or use Azure SignalR Service which guarantees per-hub ordering.

StartupRedisBackplane.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace Io.TheCodeForge.SignalR
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSignalR()
                .AddStackExchangeRedis("redis://:password@host:6380");

            // On the client side, sticky sessions are NOT required
            // because Redis relays messages across servers.
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapHub<Chat.Hubs.ChatHub>("/chat");
            });
        }
    }
}
Redis Backplane Pitfalls
Memory usage: Redis backplane queues messages for disconnected servers. If a server disconnects unexpectedly, the queue can grow unbounded. Set max buffer or reconnect gracefully. Security: Always use password-authenticated Redis connections. Never expose Redis to public networks.
Production Insight
When scaling to 4 app servers, we initially omitted the backplane. Messages from server A never reached clients on server B. Users who were load-balanced to different servers couldn't see each other's messages.
The fix: add Redis backplane and ensure clients can connect to any server without sticky sessions. We then removed the Application Request Routing sticky session rule — messages worked everywhere.
Rule: add backplane as soon as you have more than one server instance. It's not optional for multi-server deployments.
Key Takeaway
SignalR single-server mode is not a production topology for scale.
Redis backplane is simple, but Azure SignalR Service removes sticky session requirements.
Always test message propagation across servers with a distributed stress test.

Production Gotchas: Connection Drops, Memory Leaks, and Monitoring

Even with a correct setup, SignalR can silently fail. Common issues include: idle timeouts too aggressive, connection pooling exhaustion on the Redis backplane, and memory leaks from failing to dispose of clients.

Monitor these metrics: active connections (per server), messages per second, transport type distribution, and connection duration. Use Microsoft.AspNetCore.SignalR counters via dotnet-counters or Application Insights.

Another gotcha: SignalR's built-in reconnection is good but doesn't restore your user's context. If you rely on Context.Items for user identity, that is lost on reconnect. Use authentication tokens or claims to repopulate context after reconnect.

Memory leaks: each hub connection allocates memory. If clients connect and never disconnect cleanly (e.g., mobile app killed without closing WebSocket), your server can accumulate zombie connections. Set aggressive idle timeouts on both sides to clean them up.

Also watch out for the 'too many connections' issue on the Redis side. Each server instance opens a connection to Redis; if you have many instances, Redis may reach its connection limit. Use connection multiplexing or increase Redis maxclients.

One more trap: TLS renegotiation. If you're using HTTPS with client certificates, the handshake overhead per WebSocket upgrade can cause CPU spikes. Consider using a dedicated SignalR endpoint on a separate port with TLS termination at the load balancer.

HubErrorHandling.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using Microsoft.AspNetCore.SignalR;
using Io.TheCodeForge.SignalR.Monitoring;

namespace Io.TheCodeForge.Chat
{
    public class MonitoredHub : Hub
    {
        public override async Task OnConnectedAsync()
        {
            // Metric: increment active connections
            SignalRMetrics.ConnectionCount.Add(1);
            await base.OnConnectedAsync();
        }

        public override async Task OnDisconnectedAsync(Exception? exception)
        {
            SignalRMetrics.ConnectionCount.Add(-1);
            if (exception != null)
            {
                // Log exception details – helps identify silent disconnects
                SignalRMetrics.DisconnectionExceptions.Add(1, KeyValuePair.Create("Hub", GetType().Name));
            }
            await base.OnDisconnectedAsync(exception);
        }
    }
}
Idle Timeout Configuration
Default KeepAliveInterval is 15 seconds, ClientTimeoutInterval is 30 seconds (2 missed keep-alives). For long-lived connections, reduce keep-alive interval or increase timeout. Behind a load balancer, match these to the balancer's idle timeout.
Production Insight
A real-time dashboard app experienced random disconnects every 10 minutes. The cause: Azure Load Balancer had an idle timeout of 4 minutes, but SignalR endpoints were expecting a longer connection. The load balancer closed the connection, and SignalR's automatic reconnect would create a new one, but the user saw a blank dashboard for a few seconds.
The fix: set KeepAliveFrequency to 60 seconds and ClientTimeoutInterval to 120 seconds on the server, and configure the load balancer's idle timeout to 5 minutes (or use TCP keep-alive).
Rule: always align SignalR timeout settings with proxy/load balancer settings.
Key Takeaway
Idle timeouts are the top cause of random disconnections.
Monitor active connections and duration to spot disconnection patterns.
Set timeouts that are at least twice the keep-alive interval.
● Production incidentPOST-MORTEMseverity: high

The Redis Backplane Config That Took Down Our Chat Service at 50k Users

Symptom
Users connected successfully (no errors), but chat messages sent from one client never appeared for others. The server's connected client count looked normal.
Assumption
The Redis backplane was correctly configured because the app started without exceptions and connections succeeded. We assumed message delivery was working.
Root cause
The Redis connection string omitted the password field. SignalR's Redis backplane logged a one-time warning at startup — buried in verbose logs — and then silently fell back to a local in-memory bus. All messages were only visible to clients on the same server instance. Since we had two load-balanced servers, messages never crossed instances.
Fix
Added the Redis password to the connection string: "redis://:password@host:port". Then validated with docker compose logs that no backplane warnings appear. After restart, messages propagated across servers immediately.
Key lesson
  • SignalR's Redis backplane logs a single warning at INFO level when it can't connect — assume nothing; explicitly verify with a health check.
  • Add a startup validation that writes a test message through the backplane and confirms it arrives on a secondary server before declaring the deployment healthy.
  • Never rely on 'no errors' as proof of correct configuration. SignalR degrades gracefully and quietly.
Production debug guideSymptom → Action reference for the most common real-time issues4 entries
Symptom · 01
Client never receives any messages, but connection succeeds (200 response).
Fix
Check if a backplane is configured and reachable. Run docker compose logs signalr and grep for 'MessageBus'. If no Redis/ServiceBus backplane is listed, messages are per-server only. Enable SignalR server logs with Logging:LogLevel:Microsoft.AspNetCore.SignalR=Debug.
Symptom · 02
WebSocket upgrade fails with 400 status, falls back to Long Polling.
Fix
Verify the client's WebSocket support in browser (console: new WebSocket('ws://')). Check reverse proxy (nginx, IIS) for missing WebSocket upgrade headers. Add --proxy-http-version 1.1 --proxy-set-header Upgrade $http_upgrade --proxy-set-header Connection 'upgrade' in nginx.
Symptom · 03
Intermittent message loss under load spikes.
Fix
Monitor SignalR server-side buffering: context.Items may overflow. Check hub method for async void – use async Task always. If using Azure SignalR Service, verify connection limit per unit isn't exceeded.
Symptom · 04
Clients disconnect randomly every few minutes.
Fix
Check idle timeout settings: KeepAliveInterval default is 15s, ClientTimeoutInterval default is 30s. If clients are behind a proxy that has shorter timeouts, increase these intervals. Also verify WebSocket Ping frequency matches proxy expectations.
★ SignalR Connection Cheat SheetCommands and checks to run when SignalR behavior goes wrong
No messages delivered across servers
Immediate action
Check backplane connectivity
Commands
docker compose logs signalr | grep -i "redis\|backplane\|messagebus"
redis-cli -h <host> -p <port> ping (expected: PONG)
Fix now
Add correct connection string and redeploy; verify no 'Unable to connect' warnings in startup logs.
WebSocket upgrade fails+
Immediate action
Test WebSocket support on client and proxy
Commands
curl -v -H "Upgrade: websocket" -H "Connection: Upgrade" http://yourdomain.com/chat
browser console: new WebSocket('wss://yourdomain.com/chat')
Fix now
Configure proxy to pass Upgrade and Connection headers; ensure server supports WebSockets (app.UseWebSockets()).
High CPU / connection drops+
Immediate action
Check for async void or long-running hub methods
Commands
dotnet-counters monitor -p <pid> --counters Microsoft.AspNetCore.SignalR
Review hub method signatures; use `async Task` not `async void`
Fix now
Convert sync methods to async Task; avoid blocking calls inside hub methods; increase parallelism if needed.
Backplane Comparison
Backplane OptionSetup ComplexitySticky Sessions RequiredMessage Order GuaranteeCost
None (Single Server)NoneNoLocal onlyFree
Redis BackplaneMediumNo (if fan-out done properly)Best effortRedis cluster cost
Azure SignalR ServiceLowNoStrong (per hub)Per-unit pricing

Key takeaways

1
SignalR automatically negotiates transports
WebSocket > SSE > Long Polling — but each fallback costs CPU and latency.
2
Hub instances are transitory
never store state in fields; use Context.Items for per-connection state.
3
Scaling across servers requires a backplane (Redis or Azure SignalR Service); single-server mode is not production-ready.
4
Idle timeouts from load balancers are the #1 cause of random disconnections
align timeout settings.
5
Monitor active connections, transport distribution, and message throughput to catch silent failures early.
6
Group memberships are lost on reconnect
design your client to re-join groups after reconnection.

Common mistakes to avoid

6 patterns
×

Memorising syntax before understanding the concept

Symptom
Writes code that compiles but fails at runtime due to misunderstanding of underlying concepts.
Fix
Focus on understanding the problem domain and algorithm design before diving into syntax. Use pseudocode first.
×

Skipping practice and only reading theory

Symptom
Struggles to implement real-world features despite having theoretical knowledge, causing slow task completion.
Fix
Alternate between reading a concept and building a small project that uses it. Practice coding every day.
×

Using async void in hub methods

Symptom
SignalR silently loses exceptions and the connection may drop; method returns before completing.
Fix
Always return Task (or Task<T>) from hub methods. If you need fire-and-forget, wrap in a background task and catch exceptions.
×

Forgetting to configure a backplane in multi-server deployments

Symptom
Messages sent from server A never reach clients on server B. Only a subset of users receive updates.
Fix
Add a Redis or Azure SignalR Service backplane. Validate with a simple ping-pong test between servers.
×

Not handling connection reconnect and group re-joining

Symptom
After a brief network interruption, users stop receiving messages because they are no longer in any groups.
Fix
On the client, after reconnection, call server methods to re-join the necessary groups. Store group membership in client-side storage (e.g., localStorage).
×

Missing CORS configuration for WebSocket connections

Symptom
Browser blocks WebSocket upgrade from different origins, causing fallback to Long Polling or connection failure.
Fix
In ASP.NET Core, add CORS policy with AllowCredentials() and AllowAnyHeader(). Ensure the allowed origin matches the client's origin exactly.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain the SignalR transport negotiation process. What is the fallback ...
Q02SENIOR
How do you scale SignalR across multiple servers? Compare Redis backplan...
Q03SENIOR
What is the hub connection lifetime? How do you maintain per-connection ...
Q04SENIOR
Why would SignalR clients disconnect randomly every few minutes, even wh...
Q05SENIOR
How can you monitor SignalR performance and detect issues early?
Q06SENIOR
What happens to group memberships when a client reconnects? How do you h...
Q01 of 06SENIOR

Explain the SignalR transport negotiation process. What is the fallback chain and why does it matter in production?

ANSWER
SignalR client sends a negotiate request listing supported transports. The server picks the best available: WebSocket, then Server-Sent Events, then Long Polling. The fallback ensures connectivity even when WebSockets are blocked by proxies. In production, monitor the transport distribution — a high percentage of Long Polling connections indicates network restrictions that increase server load and latency.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What is SignalR for real-time apps in simple terms?
02
Can SignalR work without WebSockets?
03
Do I need a backplane for a single server with SignalR?
04
How do I handle user reconnection and restore their state?
05
Why are my messages not reaching all clients?
06
What is the difference between KeepAliveInterval and ClientTimeoutInterval?
🔥

That's ASP.NET. Mark it forged?

7 min read · try the examples if you haven't

Previous
Dependency Injection in ASP.NET Core
7 / 14 · ASP.NET
Next
Blazor Basics