What Does TCP listen() Backlog Actually Control on Linux?

What does the second argument to listen() control?
listen(sockfd, backlog);You will find three answers: the queue of incomplete handshakes, the queue of completed connections waiting for accept(), or the sum of both. On modern Linux, the accurate answer needs two layers.
At the API level,
backlogsets the nominal capacity of the accept queue. In Linux 6.10, the same stored value also participates in deciding when incomplete SYN requests are considered full, so it indirectly affects SYN-cookie or drop behavior too.
That distinction resolves most contradictions. Linux changed the public meaning in 2.2, while newer kernels also reuse the accept-backlog threshold inside request-queue logic.
This article follows the ordinary TCP listen path in Linux 6.10. TCP Fast Open and request migration under SO_REUSEPORT add branches but do not change the basic distinction.
Two connection populations
Linux 2.2 changed listen() semantics. Before that release, backlog described incomplete connection requests. Since 2.2, the listen(2) manual defines it as the length of the queue for sockets that have completed the handshake and wait for accept().
Modern Linux still has two logical populations:
- Incomplete requests. After receiving a SYN and sending a SYN-ACK, the server normally tracks a
request_sockinTCP_NEW_SYN_RECVwhile waiting for the final ACK. - Completed connections. After validating the ACK and creating a full socket, the kernel queues it for the application to accept.
“SYN queue” remains useful engineering shorthand, but it is no longer necessarily one private linked list. Since Linux 4.4, ordinary request sockets live in the TCP established hash table while the listening socket retains counters and request-queue state.
An incomplete request can also live much longer than one RTT. With the default tcp_synack_retries=5, retransmission backoff can keep an unresponsive request around for roughly a minute.
How backlog becomes sk_max_ack_backlog
The listen() syscall first clamps the supplied value to net.core.somaxconn:
somaxconn = READ_ONCE(sock_net(sock->sk)->core.sysctl_somaxconn);
if ((unsigned int)backlog > somaxconn)
backlog = somaxconn;For a normal nonnegative argument:
effective_backlog = min(application_backlog, net.core.somaxconn)Linux 5.4 raised the default somaxconn from 128 to 4096. Passing 65,535 from an application still produces 4096 when the system setting remains 4096.
The protocol listen path writes the clamped value to sk->sk_max_ack_backlog. The current number of completed sockets waiting for acceptance is sk->sk_ack_backlog. This is the direct evidence that the public backlog parameter describes the accept queue.
The surprising greater-than check
In Linux 6.10, the accept queue is considered full with >, not >=:
static inline bool sk_acceptq_is_full(const struct sock *sk)
{
return READ_ONCE(sk->sk_ack_backlog) >
READ_ONCE(sk->sk_max_ack_backlog);
}The normal path checks and then increments. A listener whose nominal capacity is 128 can therefore briefly show Recv-Q=129 and Send-Q=128 in ss. The effective backlog is a nominal threshold, not a strict mathematical maximum that can never be exceeded.
Why the same value affects incomplete requests
Linux 6.10 also compares the incomplete-request count with sk_max_ack_backlog:
static inline int inet_csk_reqsk_queue_is_full(const struct sock *sk)
{
return inet_csk_reqsk_queue_len(sk) >=
READ_ONCE(sk->sk_max_ack_backlog);
}That comparison helps decide when SYN cookies or drops are needed. tcp_max_syn_backlog still matters, but it is not a universal independent hard cap. When SYN cookies are disabled and the request count reaches the last quarter of tcp_max_syn_backlog, Linux can drop new SYNs from peers that have not demonstrated reachability, preserving room for peers it trusts more.
The useful mental model is therefore:
backlogdirectly sets the nominal accept-queue threshold;- the kernel also uses that threshold in incomplete-request pressure logic;
tcp_max_syn_backlogand SYN-cookie policy add other limits and behavior.
What happens when the accept queue is full
New SYNs and final handshake ACKs take different paths.
A new SYN arrives
tcp_conn_request() checks the accept queue. If it is full, the kernel increments ListenOverflows and drops the SYN; the surrounding listen-drop path also increments ListenDrops. The client must wait for a SYN retransmission.
This check occurs after the request-pressure decision. Even when SYN cookies would otherwise be available, a full accept queue means the application is not draining completed connections. Cookies reduce state held for incomplete requests; they cannot make the application call accept() faster.
The final ACK arrives
When a tracked request receives its final ACK, Linux attempts to create a full child socket. If the accept queue is full, that creation fails and increments the listen overflow/drop counters.
What happens next depends on net.ipv4.tcp_abort_on_overflow.
With the default value 0, Linux does not immediately reset the connection. It keeps the request_sock, marks that an ACK was seen, and can retransmit the SYN-ACK later. The client, which may already consider the connection established, responds with another ACK. If the accept queue has space by then, the server can finish creating the connection.
This creates a temporary state mismatch:
- client
connect()may already have returned successfully; - the server still has a
TCP_NEW_SYN_RECVrequest rather than a full accepted socket; - early application data may wait for retransmission and recovery;
- if pressure persists, the client may time out first.
With tcp_abort_on_overflow=1, Linux sends a reset and removes the request. The client fails faster, but connections that could have recovered after a short burst are sacrificed.
Kernel documentation advises enabling this only after verifying that the listener cannot be made to accept faster. It is a failure-policy decision, not a generic microservice tuning recommendation.
SYN cookies solve a different problem
Linux 6.10 checks tcp_syncookies when processing a new SYN:
0: do not use SYN cookies; drop when the request threshold is reached;1: use cookies when request pressure reaches the threshold; this is the default;2: use cookies unconditionally, mainly for testing.
On the cookie path, Linux temporarily allocates a request to parse options and construct the SYN-ACK, encodes connection state into the server’s initial sequence number, sends the response, and frees the request. When the ACK returns, the server validates the cookie and creates a full socket only if the accept queue has room.
Cookies protect the server from retaining state for every spoofed SYN. They do not absorb an unlimited rate of legitimate completed connections and do not fix a stalled accept loop.
Diagnose the queue that is actually failing
Inspect the accept queue
For a listening socket, ss -lnt reports:
| Field | Meaning for LISTEN |
|---|---|
Recv-Q |
Current completed connections waiting for accept(), or sk_ack_backlog |
Send-Q |
Nominal accept backlog, or sk_max_ack_backlog |
Example:
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 129 128 0.0.0.0:8080 0.0.0.0:*That snapshot shows the full condition, but one snapshot cannot prove that connections were dropped. Observe counter deltas:
nstat -az TcpExtListenOverflows TcpExtListenDrops- growing
ListenOverflowsmeans an accept-queue full check fired; - growing
ListenDropsmeans the listening path dropped a request, from a somewhat broader set of causes.
The values often rise together, but do not assume they are permanently equal.
Inspect incomplete requests and cookies
List requests in SYN-RECV:
ss -nt state syn-recv '( sport = :8080 )'Then observe request-pressure counters:
nstat -az TcpExtTCPReqQFullDoCookies TcpExtTCPReqQFullDropTCPReqQFullDoCookiesmeans the request threshold triggered SYN cookies;TCPReqQFullDropmeans the threshold was reached and the request was dropped without a cookie.
Packet captures complete the picture. Repeated SYNs, repeated SYN-ACKs, silence after the final ACK, application-data retransmissions, and RSTs distinguish different failure stages.
Set backlog from load, not folklore
There is no universal correct number. Estimate the burst the listener must absorb from:
peak new connections per second × longest acceptable accept pauseThen validate memory use, client timeout behavior, and tail latency under realistic bursts. A deeper queue absorbs longer pauses but can also hide sustained overload by making clients wait inside the kernel.
A practical tuning sequence is:
- Confirm the application’s
listen()value and whethersomaxconnclamps it. - Monitor deltas for
ListenOverflows,ListenDrops,TCPReqQFullDoCookies, andTCPReqQFullDropalongside connection rate and latency. - Find why acceptance slowed: event-loop wakeup or drain stalls, listener scheduling, process pauses, descriptor exhaustion, or memory pressure.
- Only then increase backlog/somaxconn, add listeners, or use
SO_REUSEPORT, and repeat the load test. - Consider
tcp_abort_on_overflow=1only when immediate failure is better than recovery and upstream retry behavior is proven.
In modern Linux API semantics, backlog controls the nominal accept queue. In Linux 6.10 implementation details, the stored value also influences incomplete-request pressure. Keeping those layers separate is the key to understanding backlog, somaxconn, tcp_max_syn_backlog, SYN cookies, and overflow counters without relying on stale queue diagrams.
References
- Linux 6.10,
listen()syscall implementation - Linux 6.10,
sk_max_ack_backlogassignment - Linux 6.10, accept-queue full check
- Linux 6.10, incomplete-request threshold
- Linux 6.10, TCP request and SYN-cookie handling
- Linux 6.10, IPv4 accept-overflow handling
- Linux 6.10,
tcp_abort_on_overflowbranch - Linux 6.10, networking sysctl documentation
- Linux man-pages,
listen(2)


