Linux caps listen backlog with somaxconn; high-concurrency tuning also hinges on SYN queues, ephemeral ports, and file limits. Full breakdown.

Listen backlog and somaxconn

When a service calls listen(), it sets a backlog for incomplete and ready connections waiting to be accepted. Linux caps that backlog with net.core.somaxconn. If your application requests a large backlog but the kernel limit is lower, the effective queue is the smaller value. Under bursty traffic, a short backlog causes refused connections even when the process is healthy—clients see resets or timeouts while accept workers never get a chance to drain the queue.

Raise somaxconn so it matches (or slightly exceeds) what your process actually needs, then set the listen backlog in the application or reverse proxy to the same order of magnitude. After a change, confirm with ss -ltn that the Listen queue depth reflects the new limit, not the old default. Tuning only one side leaves the other as the bottleneck.

SYN queues and incomplete handshakes

Incoming TCP connections spend time in the SYN queue before the three-way handshake finishes and they move to the accept queue. The size of that path is controlled mainly by net.ipv4.tcp_max_syn_backlog and related tcp memory settings. If the SYN queue fills under load—or under a flood of incomplete handshakes—new SYNs are dropped even though your accept loop is free. That failure mode looks like intermittent connect failures and is easy to misread as an application bug.

For web infra behind load balancers, keep SYN cookies enabled so the kernel can continue accepting when the queue is under pressure, and size tcp_max_syn_backlog in line with expected concurrent handshakes, not idle connection count. Watch netstat -s or /proc/net/netstat for SYN drops and listen overflows; those counters tell you whether you need more queue, faster accepts, or both.

Ephemeral ports and outbound concurrency

Every outbound connection from a host uses a local ephemeral port. The default range is finite. Under heavy reverse-proxy, service-mesh, or client-side fan-out traffic, you can exhaust local ports and see failures that look like “connection refused” or “cannot assign requested address.” Expanding net.ipv4.ip_local_port_range increases the pool; shortening TIME_WAIT with careful use of reuse settings can free ports faster, but reuse has correctness tradeoffs for some protocols and should not be flipped blindly.

Also check whether many short-lived connections are necessary. Connection pooling, keep-alives, and fewer hop-by-hop TLS sessions reduce port pressure more reliably than kernel knobs alone. Treat ephemeral-port tuning as capacity planning for concurrent outbound sockets, not a substitute for connection reuse.

File descriptors and process limits

Each accepted socket, open log, and upstream connection costs a file descriptor. Kernel-wide fs.file-max and per-process ulimit -n (or systemd LimitNOFILE) must both clear the ceiling you need. If the process hits its soft limit first, new accepts fail with “Too many open files” while the system still has headroom. If the system-wide limit is lower, every process competes for the same pool.

  • Set process limits for web servers and proxies above peak concurrent sockets plus headroom for files and upstreams.
  • Align fs.file-max (and related fs.nr_open) so the kernel will not cap well-tuned services.
  • Verify the running process actually inherited the new limit—service managers and containers often keep older soft limits until restart.

High-concurrency web hosts usually need all four areas in sync: backlog and SYN queues for inbound bursts, ephemeral ports for outbound fan-out, and file limits so sockets can exist at all. Change one layer, measure queue drops and errors under load, then adjust the next—isolated tweaks rarely hold when traffic spikes.

Automate Your Content with AI Video Generator

Try it Free →