Skip to content
shellhacks.dev
Performance

How to Tune Linux for Many Connections With Real Output

Tune Linux for many connections: file descriptor limits, listen backlog, port range, TIME_WAIT, conntrack and the output of ss that shows which limit you hit.

7 min read
Summarize in AI
Illustration for "How to Tune Linux for Many Connections With Real Output": a terminal session, a loop arrow, a table of open ports.

Most "tune your kernel for 100,000 connections" guides are lists of sysctl values copied between blog posts. A modern kernel's defaults are good, and what usually bites is one of four specific limits, each with a visible symptom. This post identifies them by their symptoms and shows how to raise the right one. It is a companion to debugging high load and the page cache. Examples use Linux 6.12 on Debian 13 with nginx 1.26.

Measure before changing

Shell
ss -s
cat /proc/sys/fs/file-nr
ss -tan state time-wait | wc -l
sudo dmesg | grep -Ei 'conntrack|syn flooding|too many'
Text
Total: 48211
TCP:   46500 (estab 12040, closed 33100, orphaned 12, timewait 33088)

ss -s summarizes sockets by state; a large timewait count is normal on a busy server and not a problem by itself. file-nr shows allocated, free and maximum file handles system-wide. The kernel log names the limits it hit. A guess without these numbers changes things that were never the bottleneck.

1. Open files per process

Shell
cat /proc/$(pgrep -o nginx)/limits | grep -i 'open files'
/etc/systemd/system/nginx.service.d/limits.confINI
[Service]
LimitNOFILE=200000

The error is Too many open files (EMFILE) in the application's log. Each connection takes a descriptor, a proxy takes two (client and upstream), and the default soft limit is often 1024. For services, the limit comes from the unit, not from /etc/security/limits.conf, which applies to login sessions. After systemctl daemon-reload and a restart, check /proc/PID/limits. The application's own setting must match: nginx needs worker_rlimit_nofile and worker_connections. The system-wide ceiling is fs.file-max and per-process fs.nr_open.

2. The listen backlog

/etc/sysctl.d/90-net.confText
net.core.somaxconn = 8192
net.ipv4.tcp_max_syn_backlog = 8192
net.core.netdev_max_backlog = 16384
Shell
sudo sysctl --system
ss -ltn
nstat -az TcpExtListenOverflows TcpExtListenDrops

When connections arrive faster than the application accepts them, they queue, and when the queue is full, new ones are dropped or reset. The symptom is climbing ListenOverflows and clients seeing timeouts under bursts. somaxconn caps the accept queue (the default is 4096 since Linux 5.4, and was 128 before), but the application must request it: nginx has listen 80 backlog=8192;. ss -ltn shows Recv-Q (current queue) and Send-Q (the limit) for listening sockets.

3. Ephemeral ports

Shell
sysctl net.ipv4.ip_local_port_range
sudo sysctl -w net.ipv4.ip_local_port_range="10240 65000"

A client or proxy making many outgoing connections to the same address and port can use only as many as there are free local ports: about 28,000 with the default range of 32768 to 60999. The symptom is Cannot assign requested address (EADDRNOTAVAIL), together with many sockets in TIME_WAIT. Widening the range helps; reusing connections (HTTP keep-alive, connection pools) helps more. net.ipv4.tcp_tw_reuse=1 allows reuse of TIME_WAIT sockets for outgoing connections and is safe for clients; avoid the removed tcp_tw_recycle advice in old guides.

4. Connection tracking

Shell
sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max

Firewalls and NAT track every connection in a table. When it is full, the kernel logs nf_conntrack: table full, dropping packet and new connections fail while existing ones continue. Raise net.netfilter.nf_conntrack_max (and the hash size), shorten timeouts for idle states, or exempt busy traffic with a NOTRACK rule if you do not need stateful filtering. Docker hosts and NAT gateways hit it most.

Verify under load

Shell
wrk -t4 -c10000 -d30s http://127.0.0.1:8080/
ss -s

Test by generating the load you expect, with a tool such as wrk or hey from another machine, and watch the counters from step one. Change one setting at a time, record what it did and revert what had no effect. Persist the values in /etc/sysctl.d/, and write a comment saying which symptom each one fixed.

Where this does not apply

If the CPU is saturated or the application serializes requests, none of this helps: profile first, as in perf. Very large buffer settings (tcp_rmem, tcp_wmem) and congestion algorithms matter for long, fat networks and little elsewhere. Containers inherit some sysctls from the host and others are per-namespace.

Why defaults run out and what the symptoms look like

A server that handles thousands of concurrent connections meets several limits, each with its own symptom, and the way to tune is to identify which one you hit and raise only that. Running out of file descriptors shows as "Too many open files" errors in the application log. A full listen queue shows as connection attempts that time out or reset while the server looks idle, and drops are counted in kernel statistics. Running out of local ports on a client or proxy shows as "Cannot assign requested address" when making outbound connections. A full connection tracking table shows as dropped packets with a kernel message in the log about the table being full, and affects any machine that does network address translation or runs a stateful firewall. Blindly raising every number you find in a tuning guide is a common mistake. It wastes memory, hides the real bottleneck and sometimes makes behavior worse, so start from evidence.

Collect the evidence with the tools: ss -s for socket totals and states, nstat or netstat -s for counters of listen overflows and drops, and the kernel log for conntrack messages.

Descriptors and the backlog in more detail

Each connection uses a file descriptor, and the limit has layers. A per-process soft limit, shown by ulimit -n, is often 1024 by default. A hard limit caps what the process may raise it to. A system-wide ceiling, fs.file-max, limits the total. For a service, set LimitNOFILE= in the systemd unit, since limits from your shell or from limits.conf do not apply to services started by systemd. Verify the limit a running process actually has by reading /proc/PID/limits. The listen backlog is the queue of connections that completed the handshake and wait for the application to accept them. The effective size is the smaller of the number the application passes to the listen call and the kernel's net.core.somaxconn, so raising the sysctl alone changes nothing if the application asks for 128, and the application's own setting, such as the backlog option in a web server, must be raised too. The related net.ipv4.tcp_max_syn_backlog governs half-open connections, and the defaults suit most loads.

Ports, TIME_WAIT and connection tracking

An outbound connection needs a local port, drawn from the range in net.ipv4.ip_local_port_range, about 28,000 ports by default. A single client address connecting to a single server address and port can have at most that many simultaneous connections, plus those lingering in the TIME_WAIT state for sixty seconds after closing, which count against the same pool. A proxy or load balancer making many short connections to a few backends can exhaust it. Widen the range, reuse connections with keep-alive so fewer are created, spread traffic across more backend addresses, and consider net.ipv4.tcp_tw_reuse for outbound connections, which allows reuse of TIME_WAIT sockets under safe conditions. Do not enable the removed tcp_tw_recycle option from old guides. For connection tracking, nf_conntrack_max sets the table size, and the current count is in /proc/sys/net/netfilter/nf_conntrack_count. Raising the maximum uses kernel memory, and shorter timeouts for idle connections reduce the pressure without a larger table.

Make changes persistent in a file under /etc/sysctl.d, apply with sysctl --system, and record each value with the reason, so the next person knows it was chosen.

Proving the effect under load

Change one setting at a time and test with a load generator that opens the number of connections you expect, such as a benchmarking tool for HTTP or a simple script that holds sockets open. Watch the counters that showed the problem and confirm they stop increasing, and watch memory, since each established connection uses kernel and application memory, often tens of kilobytes or more. Compare latency percentiles and errors before and after. If a setting made no difference, revert it, because configuration you do not understand is a risk. Remember that a tuned server still depends on its neighbors: a load balancer, a firewall or the other end of the connection may have its own limits that cap throughput well below yours. Keep the tuning notes with the machine's configuration in version control, and re-test after kernel upgrades, since defaults change between versions and a value that was needed once may no longer be.

See the kernel IP sysctl documentation and socket(7).

Also inLinux
Taggedlinux
Share

Keep reading

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.