Diagnosing Blazor Server 'Circuit Disconnected' Failures: Causes, Checks, and Fixes
The yellow 'reconnecting' banner in Blazor Server means the SignalR circuit died. Learn to read the timing, check the _blazor WebSocket, and match fixes—retention tuning, async handlers, proxy timeouts, sticky sessions—to the actual cause.
24 Jun 2026, 15:26 UTC

If your Blazor Server app shows a yellow "Attempting to reconnect to the server" banner — or worse, a modal saying the connection could not be restored — the problem is not cosmetic. Every click and event in Blazor Server travels over a live SignalR circuit, so when that circuit dies, the UI freezes. This guide helps you identify which of the handful of common causes you're hitting, in an order that matches how they actually present.
Recognizing the condition
The symptom is always the same from the user's side: the app renders, then stops responding. But the timing of the disconnect is your first diagnostic signal:
- Disconnects after a predictable idle period (e.g., always around 60–100 seconds) point to a proxy or load-balancer idle timeout.
- Disconnects when the laptop sleeps or the tab is backgrounded point to the browser or OS suspending the WebSocket.
- Disconnects during heavy UI work or after clicking a button that triggers slow processing point to a blocked dispatcher on the server.
- Disconnects under load, at random point to memory pressure evicting circuits, or to scale-out without sticky sessions.
Cause table
| Cause | Typical signature | Where to confirm |
|---|---|---|
| Proxy idle timeout kills the WebSocket | Disconnects at a fixed interval | Browser dev tools → Network → _blazor WS close frame; proxy config |
| Idle tab / device sleep | Disconnect after inactivity or lock screen | Reproduce by backgrounding the tab |
| Server restart or missing sticky sessions | Disconnects correlate with deploys or instance changes | Server logs; load-balancer affinity settings |
| Memory pressure evicts circuits | Disconnects under concurrent load | Process memory metrics; CircuitHost log entries |
| Synchronous blocking in event handlers | UI freezes during long operations, then drops | Code review for .Result/.Wait; server logs |
Ordered checks
- Reproduce and time it. Note whether the disconnect is immediate, after N minutes idle, or during activity. This narrows the table above to one or two rows.
- Open browser dev tools → Network, filter for
_blazor. Inspect the WebSocket close code. A clean server-initiated close looks different from an abrupt transport failure, and the timing of the close frame tells you whether something in the middle (a proxy) terminated it. - Check server logs for circuit entries. Enable Information-level logging for
Microsoft.AspNetCore.Components.Server. Circuit start/end entries (from CircuitHost) show whether termination was initiated by the server (eviction, shutdown) or simply observed (client vanished). - Review infrastructure timeouts. Check every hop between browser and app: nginx, Azure Front Door, Cloudflare, AWS ALB. Any idle timeout shorter than the WebSocket's keepalive behavior will sever idle connections.
Fixes tied to findings
Idle tabs and brief network loss: tune reconnection and retention
Blazor Server already retries reconnection, but the server only holds a disconnected circuit for a limited window. You can widen that window so brief drops recover transparently:
// Program.cs (.NET 6+ minimal hosting)\nbuilder.Services.AddServerSideBlazor(options =>\n{\n // How long a disconnected circuit is kept for reconnection\n options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(3);\n // Cap on how many disconnected circuits are retained at once\n options.DisconnectedCircuitMaxRetained = 100;\n});Run this where your app is configured (Program.cs); it requires no special permissions but does change server memory behavior. Trade-off: every retained circuit holds user state in server memory, so a longer retention period multiplied by many idle clients raises memory usage. Treat DisconnectedCircuitMaxRetained as your safety valve. Also customize the reconnect UI (the components-reconnect-modal element or a handler in Blazor.start) so users see progress instead of a frozen page.
Blocked dispatcher: stop doing synchronous work on the UI thread
If disconnects follow slow operations, look for blocking calls in event handlers:
// Bad: blocks the renderer synchronization context\nvar data = httpClient.GetStringAsync(url).Result;\n\n// Good: awaits, keeping the circuit responsive\nvar data = await httpClient.GetStringAsync(url);Any .Result, .Wait(), or long CPU-bound loop inside a component event handler starves the circuit's heartbeat. Move heavy work to a background service and report progress back via InvokeAsync(StateHasChanged).
Proxy timeouts and scale-out: fix the infrastructure
For nginx, raise the WebSocket idle timeout and ensure upgrade headers are set:
location / {\n proxy_pass http://blazor_app;\n proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_read_timeout 3600s; # was likely 60s\n}Apply this on the proxy host (requires reload of nginx, e.g., nginx -s reload). If you run multiple app instances, enable sticky sessions at the load balancer — circuits live in one server's memory and cannot migrate to another, so a request routed to a different instance is a dead circuit.
Escalation criteria
If disconnects persist after you've fixed timeouts, sticky sessions, and blocking code, the remaining suspect is capacity. Capture process memory and active circuit counts under load. Frequent circuit eviction during traffic spikes means you're trading retention for survival — the fixes are horizontal scaling (with sticky sessions) or moving per-user state out of the circuit into distributed storage so circuits become cheap to lose.
Verifying the fix
- In dev tools, use the Network tab's offline toggle or throttling to kill the connection, then restore it. The app should recover within your retention window without a full reload.
- Confirm in server logs that the circuit resumed rather than starting fresh (a fresh circuit loses component state).
- Load-test with concurrent idle clients and watch memory: retention settings should not push the process toward eviction under your expected idle-user count.
Note: This applies to Blazor Server (or interactive server components) only — Blazor WebAssembly has no server circuit. Circuit option defaults and reconnection APIs vary across .NET versions, so verify against the version you actually deploy.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.