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

Incoming TCP connections split between a SYN request queue and an accept queue on a Linux server

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, backlog sets 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:

  1. Incomplete requests. After receiving a SYN and sending a SYN-ACK, the server normally tracks a request_sock in TCP_NEW_SYN_RECV while waiting for the final ACK.
  2. 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:

  • backlog directly sets the nominal accept-queue threshold;
  • the kernel also uses that threshold in incomplete-request pressure logic;
  • tcp_max_syn_backlog and 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_RECV request 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 ListenOverflows means an accept-queue full check fired;
  • growing ListenDrops means 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 TcpExtTCPReqQFullDrop
  • TCPReqQFullDoCookies means the request threshold triggered SYN cookies;
  • TCPReqQFullDrop means 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 pause

Then 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:

  1. Confirm the application’s listen() value and whether somaxconn clamps it.
  2. Monitor deltas for ListenOverflows, ListenDrops, TCPReqQFullDoCookies, and TCPReqQFullDrop alongside connection rate and latency.
  3. Find why acceptance slowed: event-loop wakeup or drain stalls, listener scheduling, process pauses, descriptor exhaustion, or memory pressure.
  4. Only then increase backlog/somaxconn, add listeners, or use SO_REUSEPORT, and repeat the load test.
  5. Consider tcp_abort_on_overflow=1 only 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

  1. Linux 6.10, listen() syscall implementation
  2. Linux 6.10, sk_max_ack_backlog assignment
  3. Linux 6.10, accept-queue full check
  4. Linux 6.10, incomplete-request threshold
  5. Linux 6.10, TCP request and SYN-cookie handling
  6. Linux 6.10, IPv4 accept-overflow handling
  7. Linux 6.10, tcp_abort_on_overflow branch
  8. Linux 6.10, networking sysctl documentation
  9. Linux man-pages, listen(2)