Many operations engineers have encountered this scenario: server CPU utilization is under 30% and there is plenty of free memory, yet the website lags and API requests time out. After some troubleshooting, the culprit turns out to be a limit on the number of connections.
Where does the problem lie? Linux's default TCP connection limits are designed for general-purpose scenarios, not for high-concurrency workloads. The default `ulimit -n` is only 1024, and `somaxconn` is just 128; these parameters quickly become bottlenecks as traffic increases. To make matters worse, these limits exist across three layers—user, system, and kernel—and a misconfiguration at any level can cause a bottleneck.
This article begins with assessment methods and then explains step-by-step which factors constrain TCP connection counts, how to tune them, and how to verify the changes.
First, let's clarify: what exactly limits the number of TCP connections?
A TCP connection on a Linux system consumes far more than just a single port. Each time a connection is established, the system must allocate a file descriptor (fd) and use kernel memory to maintain the socket structure; if connection tracking (conntrack) is enabled, an entry is also created in the conntrack table. Furthermore, when the server receives a new connection, the packet must pass through the SYN Queue (half-open connections) and the Accept Queue (fully established connections) before the application can accept it.
This means the upper limit for TCP connections is determined by multiple layers; if any single layer hits its limit first, the total number of connections is capped. Common bottlenecks, from the lowest level up, include: file descriptor limits, local port ranges (for client scenarios), conntrack table capacity, TCP connection queues, and application processing capacity.
Step 1: Determine the system's current "ceiling"
Before making adjustments, check the current values at each level to avoid blind modifications.
User-level file descriptor limits are the most common bottleneck. Run `ulimit -n` to view the limit for the current shell session; the default is usually 1024. This means a single process can open a maximum of 1024 file descriptors. After accounting for fixed overhead—such as stdin, stdout, stderr, and listening sockets—the number of connections actually available for use is only around 1014.
The system-wide maximum limit can be checked via `cat /proc/sys/fs/file-max`. This represents the total number of file descriptors that can be opened by all processes across the entire system. If the user-level hard limit exceeds this value, the lower of the two limits takes effect.
The limit for the TCP fully established connection queue is controlled by `net.core.somaxconn`, with a default value typically set to 128. The `backlog` parameter passed when an application calls `listen()` cannot exceed this value; configuration directives like `listen ... backlog=` in services such as Nginx are subject to this constraint.
The limit for the half-open connection queue (SYN queue) is controlled by `net.ipv4.tcp_max_syn_backlog`, with a default value ranging from 128 to 256 on most distributions. In high-concurrency scenarios, if a sudden surge of SYN requests exceeds the queue capacity, subsequent connection attempts will be dropped, resulting in connection timeouts on the client side.
The local port range affects a client's ability to initiate connections. `net.ipv4.ip_local_port_range` determines the number of source ports available for outbound connections; the default range is usually 32768–60999, providing approximately 28,000 ports. In scenarios involving proxies or web crawlers that rely heavily on short-lived connections, port exhaustion can lead to "Cannot assign requested address" errors.
Step 2: Tuning Core Parameters
Once the current state is understood, targeted adjustments can be made. It is recommended to place the following parameters in separate configuration files within the `/etc/sysctl.d/` directory rather than modifying `/etc/sysctl.conf` directly, to avoid conflicts with other configurations.
Multi-level adjustment of file descriptors. User-level limits are configured via `/etc/security/limits.conf`:
soft nofile 65535
hard nofile 65535
root soft nofile 65535
root hard nofile 65535
Note that changes take effect only in new login sessions; simply running the `source` command or restarting the service is insufficient—you must reconnect via SSH or switch users. The system-wide limit is adjusted via `fs.file-max`; it is recommended to set this to approximately 10% of the physical memory (in KB).
Expanding TCP queues. The fully established connection queue and the half-open connection queue (SYN queue) both require significant expansion for high-concurrency scenarios:
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
Increasing `somaxconn` from the default 128 to 65535 allows the backlog parameters of application-layer services (such as Nginx) to function effectively. By applying this adjustment during a promotional event, an e-commerce platform increased its QPS from 30,000 to 100,000 and reduced P99 latency from 2 seconds to 500 milliseconds.
Optimization of the `TIME_WAIT` state. In scenarios involving short-lived connections, a large number of connections enter the `TIME_WAIT` state (which lasts 60 seconds by default), consuming port resources. Enabling port reuse can significantly alleviate this issue:
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
Setting `tcp_tw_reuse=1` allows the kernel to reuse ports in the `TIME_WAIT` state for new connections; however, this is safe only for the party actively initiating the connection (the client role). This parameter is of little significance if the server is acting as a listener accepting connections. `tcp_fin_timeout` controls the duration of the `FIN-WAIT-2` state; reducing it from the default 60 seconds to 15 seconds accelerates port reclamation.
It is crucial to note that the `net.ipv4.tcp_tw_recycle` parameter has been removed from modern kernels and causes connection issues in NAT environments. Do not enable it under any circumstances.
Expansion of the local port range. For scenarios requiring a large number of outbound connections (such as reverse proxies, web crawlers, or API gateways), expand the port range:
net.ipv4.ip_local_port_range = 1024 65535
This expands the range from the default of approximately 28,000 ports to about 64,000, more than doubling the capacity.
Connection tracking table capacity. If the server has enabled the `conntrack` feature of `iptables` or `nftables`, the default value for `net.netfilter.nf_conntrack_max` may be insufficient. It is recommended to set this to 1,048,576 or adjust it based on the application's concurrency volume. Ensure the `nf_conntrack` module is loaded before making adjustments.
A complete reference configuration for `/etc/sysctl.d/99-tcp-tuning.conf` is as follows:
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.ip_local_port_range = 1024 65535
net.core.netdev_max_backlog = 65535
net.ipv4.tcp_syncookies = 1
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
After writing the configuration, execute `sysctl -p /etc/sysctl.d/99-tcp-tuning.conf` to apply the changes.
Step 3: Synchronized adjustment of application-layer parameters
Even after tuning kernel parameters, bottlenecks may still occur at the application layer if the application's own connection limits are not adjusted accordingly.
For Nginx, `worker_connections` and `listen backlog` need to be adjusted in tandem. `worker_connections` determines the maximum number of connections each worker process can handle, while `listen 80 backlog=65535` ensures the fully established connection queue matches the kernel parameters. A common configuration combination is `worker_processes auto; worker_connections 4096;`.
Tomcat's `acceptCount` parameter corresponds to the length of the fully established connection queue; the default value of 100 is far lower than the tuned kernel parameters and needs to be increased accordingly.
The default value for Redis's `tcp-backlog` is 511; in high-concurrency scenarios, this also needs to be raised to 65535 to match the kernel queue length. Step 4: Verification and Monitoring
After tuning, verify the results using the following commands:
Confirm the parameters have taken effect:
sysctl net.core.somaxconn
sysctl net.ipv4.tcp_max_syn_backlog
ulimit -n
View the current distribution of TCP connection states:
ss -s
Check for queue overflows:
netstat -s | grep -i "listen"
In the output of `netstat -s | grep -i listen`, if the values for "times the listen queue of a socket overflowed" and "SYNs to LISTEN sockets dropped" continue to rise, it indicates the queue capacity is still insufficient. Additionally, if the `Recv-Q` value approaches the `Send-Q` value in the `ss -lnt` output, it indicates that the full connection queue is full.
One metric worth monitoring is the proportion of `TIME_WAIT` connections. While a certain number of `TIME_WAIT` connections is normal (expected TCP protocol behavior), a situation where they consume excessive port resources and continue to increase warrants attention. On a CTyunOS server, adjusting TCP recycling parameters and enabling port reuse reduced the number of `TIME_WAIT` connections by approximately 60% and increased concurrent connection handling capacity from around 15,000 to 25,000—an improvement of about 67%.
Further Thoughts on "Reasonable Connection Counts"
After tuning the parameters, let's return to the initial question: what exactly is a reasonable number of TCP connections?
The answer depends on three variables: memory (each connection consumes anywhere from a few KB to tens of KB of kernel memory), the file descriptor limit (usually no longer a bottleneck after tuning), and the application's actual processing capability. For an 8-core, 16GB server, memory estimates suggest that 100,000 concurrent connections would require roughly 1–2GB of memory to maintain socket structures—theoretically more than enough. However, the true determinant of a "reasonable" upper limit is whether the application can process requests from these connections in a timely manner; if Nginx worker processes are overwhelmed, a massive queue simply results in requests waiting in line.
Therefore, the goal of tuning is not to chase the "maximum number of connections," but to align the connection queue capacity with the application's processing power. A queue that is too small leads to dropped connections, while a queue that is too large causes requests to wait excessively, ultimately degrading the user experience. Underlying Infrastructure: The Foundation for TCP Connections
TCP connection tuning ultimately depends on the actual server environment. If the server's network connection is unstable, even perfectly tuned parameters cannot prevent frequent retransmissions or disconnections caused by packet loss. Furthermore, if bandwidth is monopolized by "noisy neighbors," response times during high-concurrency scenarios will deteriorate significantly.
Jtti’s cloud server solutions provide robust infrastructure support tailored for high-concurrency connection scenarios. Our Hong Kong and US nodes utilize premium CN2 GIA lines with optimized direct connectivity across major carriers, maintaining a packet loss rate below 0.1% even during peak evening hours. This low packet loss rate minimizes retransmissions and timeouts caused by network jitter, thereby reducing pressure on connection queues. With dedicated bandwidth provided as standard across the entire product line, there is no risk of your connections being queued due to neighbors saturating the bandwidth, ensuring more predictable response times under high concurrency.
Our "same-price renewal" policy guarantees that the renewal cost matches the initial purchase price; for servers hosting long-term, high-concurrency workloads, cost predictability is a crucial element of operational planning.
An optimal TCP connection limit is not merely a "maximum value" derived from parameter tuning, but a "stable value" determined through rigorous stress testing. While parameter tuning addresses system-level constraints, the true quality of a connection depends on the underlying network—specifically packet loss rates, bandwidth stability, and hardware resilience. Visit the Jtti official website to view full specifications and current promotions for our Hong Kong and US CN2 cloud servers, and choose a server with a solid foundation for your high-concurrency business.