A listening TCP socket has two queues, not one, and the sysctl this article is named after governs the queue that almost never drops anything. The backlog passed to listen(2) is the accept queue, holding connections that finished the handshake and await the application; the SYN queue holds half-open requests. Confusing them produces the wrong fix.

That matters because the famous advice, raise tcp_max_syn_backlog, is largely obsolete: tcp_syncookies defaults to 1, and as listen(2) puts it, with syncookies enabled there is no logical maximum length and that setting is ignored. What actually bites is the accept queue and how fast the application calls accept().

The backlog argument is the accept queue

listen(2) is explicit that the behaviour changed with Linux 2.2: backlog now specifies the queue length for completely established sockets waiting to be accepted, not the number of incomplete requests. The incomplete queue is bounded separately by /proc/sys/net/ipv4/tcp_max_syn_backlog. And if the backlog argument exceeds /proc/sys/net/core/somaxconn it is silently capped to that value, 4096 since Linux 5.4 and 128 before. So listen(8192) on a default host gives you 4096, and nothing logs it.

tcp(7) describes tcp_max_syn_backlog as the maximum number of queued requests that have not received an acknowledgement, defaulting to 256, raised to 1024 when memory is at least 128 MB, cut to 128 below 32 MB. ip-sysctl adds the qualifications: it is a per-listener limit, each half-open request costs about 304 bytes, and tcp_syncookies defaults to 1, sending a cookie on overflow instead of dropping.

With syncookies on, the SYN queue stops mattering

ip-sysctl documents tcp_syncookies as a fallback that must not be used to help a loaded server stand against legal connection throughput, and says syncookies seriously violate the protocol, disallow TCP extensions, and can degrade services. listen(2) goes further: with syncookies enabled there is no logical maximum length and tcp_max_syn_backlog is ignored. tcp(7) still describes the same knob as dropping requests once exceeded, which is the syncookies-off behaviour. Both pages are right about different configurations.

The honest reading is therefore: leave syncookies at 1 and stop tuning the SYN queue. The cost is that the fallback loses TCP options such as window scaling and SACK on the connections it rescues, which is a throughput problem rather than a connectivity one. If you see SYN-flood warnings while the traffic is legitimate, ip-sysctl’s own advice is to tune the other knobs, not to disable syncookies.

The accept queue is where the pressure lands

With the SYN queue neutralised, the queue that fills is the accept queue, sized by min(listen(backlog), somaxconn). Overflow behaviour depends on tcp_abort_on_overflow, default FALSE: ip-sysctl says the default lets a connection recover if the overflow was a burst, and warns that enabling the abort option can harm clients. The mechanism is the surprise: by default the kernel silently discards the final ACK, so connect() succeeds and the failure only surfaces when the first request stalls or is reset.

A bigger backlog is a delay line, not capacity. In the model, listen(128) and listen(65535) accept the same 20,000 connections in a second; the larger backlog only postpones the overflow, because the accept rate is set by the worker count.

When it breaks

The first failure is the silent one, and it is the default. A service whose accept loop is too slow overflows the accept queue, the kernel drops the final ACKs, and clients see connections that establish and then hang. Nothing is refused, so a client connection pool fills with dead sockets and the error surfaces as timeouts somewhere unrelated. Watch TcpExtListenOverflows and TcpExtListenDrops: those counters climbing while the accept rate is flat means the handler is the limit.

The second failure is the dropped SYN, which needs syncookies off to happen. A full SYN queue then drops the request and the client retransmits on a schedule whose documented endpoints are 67 seconds to the last retransmission and 131 seconds to the final timeout, so the user-visible symptom is a connect that hangs for over two minutes and then fails. Raising tcp_max_syn_backlog is a real fix in that configuration, and turning syncookies back on is a better one, if you accept the loss of TCP extensions.

Check it quickly

ss -lnt 'sport = :8443'
nstat -az TcpExtListenOverflows TcpExtListenDrops TcpExtTCPReqQFullDrop TcpExtTCPReqQFullDoCookies
sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog net.ipv4.tcp_syncookies net.ipv4.tcp_abort_on_overflow

In ss -lnt, Send-Q is the backlog the socket was created with and Recv-Q is how many established connections are queued and unaccepted, so Recv-Q sitting at Send-Q is the queue being full. The nstat counters should be zero or near it: TcpExtListenOverflows and TcpExtListenDrops are accept-queue events, while TcpExtTCPReqQFullDrop is a dropped SYN and TcpExtTCPReqQFullDoCookies is the fallback doing its job. The sysctl line shows what the kernel will honour, and if somaxconn is smaller than your listen() argument then that smaller number is your real backlog.

Sources

  • Kernel ip-sysctl documentation, somaxconn, tcp_max_syn_backlog, tcp_abort_on_overflow and the syncookies warning, checked 2026-09-16.
  • Linux listen(2), the accept queue since Linux 2.2, the silent somaxconn cap, and syncookies removing the SYN-queue bound, checked 2026-09-16.
  • Linux tcp(7), tcp_max_syn_backlog defaults by memory size and the syncookies caveats, checked 2026-09-16.