In this lesson, you will learn how to tune your operating system’s networking stack to support thousands of concurrent SignalR connections without hitting resource bottlenecks.
When managing persistent connections at scale, your application is no longer limited by your C# code, but by the operating system’s default limits on socket descriptors and buffer sizes. If your server is intended to hold 50,000+ persistent WebSockets, the default OS configuration will likely lead to SocketException errors or silent connection drops because the system runs out of file handles or lacks enough memory to buffer incoming traffic for each connection.
Scaling Socket Settings
Each connection consumes a file descriptor. On Linux-based environments, you must increase the ulimit for open files. Beyond file descriptors, you must tune the TCP receive and send buffers. These buffers dictate how much data can be "in-flight" before the OS tells the sender to slow down. If these are too small, latency spikes; if too large, you risk running out of non-paged pool memory under high concurrency.
You can configure these settings in your Program.cs or by adjusting the Kestrel server options to manage how memory is allocated for connection buffers:
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxConcurrentConnections = 100_000;
options.Limits.MaxConcurrentUpgradedConnections = 100_000;
// Adjust buffer sizes to prevent memory exhaustion under load
options.Limits.Http2.InitialConnectionWindowSize = 128 * 1024;
options.Limits.Http2.InitialStreamWindowSize = 64 * 1024;
});
OS-Level Tuning
In addition to Kestrel settings, you must optimize the kernel-level TCP stack. For high-concurrency SignalR hubs, ensure that the tcp_max_syn_backlog is increased to handle rapid connection surges and that ip_local_port_range is expanded to provide enough ephemeral ports for outgoing and incoming traffic. These adjustments ensure that the kernel isn't dropping incoming SYN packets simply because the internal queue is full before the application even sees the request.
By aligning your Kestrel limits with your OS-level networking constraints, you ensure that your SignalR hub remains responsive even when thousands of clients are connected simultaneously.
Key Takeaway: Scaling persistent connections requires balancing Kestrel’s internal concurrency limits with kernel-level TCP stack tuning to prevent resource exhaustion and packet drops.