skip to content

During a burst of new connections, clients of a Linux TCP server wait several seconds before their first request gets any response, while the server's own application logs show nothing wrong. Explain the two kernel queues behind listen() and what happens when the accept queue fills.

level: seniorimportance: should knowfreq 50%

answer

  1. two queues, not one
  2. half-open versus fully established
  3. the smaller of two numbers wins
  4. the drop is silent by default
  5. the app never saw the connection

basics

~20 s

A listening socket has two kernel queues: a SYN queue for half-open handshakes and an accept queue of completed connections waiting for the application to call accept(). When the accept queue overflows, Linux drops the client's final handshake packet by default, so the server retransmits its SYN-ACK and the client stalls for seconds.

solid answer

~50 s

`listen()` creates two queues, not one. A SYN arriving for the socket creates a half-open entry in the SYN queue, sized by `net.ipv4.tcp_max_syn_backlog`; when the client's ACK completes the handshake the connection moves to the accept queue, whose length is the smaller of the backlog argument passed to `listen()` and `net.core.somaxconn`. The application drains that second queue by calling `accept()`. If the application is too slow or the burst is too large, the accept queue fills, and Linux's default behaviour is to silently drop the completing packet — so the server retransmits its SYN-ACK after one second, two, four, and the client sits there believing it is connected while nothing reads its request. Nothing appears in the application log because the application never saw the connection. Setting `net.ipv4.tcp_abort_on_overflow=1` turns those silent drops into resets, which fails fast instead.

code

bash · 3 lines
bash
sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog
sysctl -w net.core.somaxconn=4096
nstat -az | grep -E 'ListenOverflows|ListenDrops'

go deeper

for a junior

Know that a listening socket queues incoming connections in the kernel until the program calls accept, and that the queue has a finite size set at listen time.

for a middle

Explain both queues by name and role — the SYN queue for half-open handshakes, the accept queue for completed connections — and that the accept queue is capped by the smaller of the listen backlog and net.core.somaxconn.

for a senior

Demonstrate the diagnosis: silent drops make the client stall while the server logs nothing, overflow counters confirm it, and the fix requires the sysctl, the application's backlog and a faster accept loop together.

for a principal

Own the tradeoff between queueing and shedding. Argue for a backlog you can actually drain within client timeouts, explicit admission control for the rest, and a deliberate choice about whether overload should fail fast with resets or absorb bursts silently.

## Two queues, not one The word "backlog" hides the fact that a listening socket owns two distinct kernel structures, and knowing which one is under pressure is the whole diagnosis. **The SYN queue (incomplete connections).** When a SYN arrives for a listening socket, the kernel does not yet have a connection — it has a half-open request. It replies with a SYN-ACK and records the pending request. `net.ipv4.tcp_max_syn_backlog` bounds how many such half-open requests may exist. This queue is where a SYN flood lands: an attacker sends SYNs and never completes them. Linux's defence is SYN cookies (`net.ipv4.tcp_syncookies`, enabled by default on current distributions), which encode the needed state into the sequence number so the kernel can stop storing pending requests altogether when the queue is under pressure. **The accept queue (established connections).** When the client's final ACK arrives, the handshake is complete. The connection is now fully established from TCP's point of view and moves onto the accept queue, where it waits for the application to call `accept()`. Its ceiling is `min(backlog, net.core.somaxconn)`, where `backlog` is the integer the application passed to `listen()`. Since Linux 2.2 that argument has meant the accept queue, not the sum of both queues — older documentation that says otherwise predates the split. ``` sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog ``` On kernels before 5.4 the default `net.core.somaxconn` was 128; from Linux 5.4 it is 4096. That change alone silently fixed a great many overload incidents. ## What overflow actually looks like Here is the behaviour that produces the confusing symptom. When the accept queue is full and a handshake completes, the default Linux behaviour is to **drop the packet silently** and leave the request in the SYN queue as if the ACK never arrived. The server therefore retransmits its SYN-ACK on the usual exponential schedule — roughly one second, then two, then four. Meanwhile the *client* already received the original SYN-ACK, so its `connect()` returned successfully. It believes it is connected, writes its request, and waits. From the client's seat the connection succeeded and the server is inexplicably slow. From the server application's seat nothing happened at all: `accept()` never returned this connection, so no log line, no metric, no trace span exists for it. That mismatch is the signature of accept-queue overflow, and it is why the interviewer asks the question this way. If space frees up before the retransmissions run out, the retransmitted SYN-ACK is answered and the connection quietly completes seconds late. If not, the client eventually sees a reset or a timeout. The kernel does count these events. Overflow increments `TcpExtListenOverflows` and `TcpExtListenDrops` in the network SNMP counters, and the kernel logs "possible SYN flooding" style warnings when SYN cookies engage. Rising overflow counters are the unambiguous confirmation. ## The fail-fast alternative `net.ipv4.tcp_abort_on_overflow=1` changes the policy: instead of silently dropping, the kernel sends a RST. Clients then fail immediately with "connection reset by peer" rather than hanging. That is often the better behaviour behind a load balancer that can retry another backend, because a fast, visible failure beats an invisible multi-second stall. It is worse when the overload is a brief spike that would have cleared on its own, since the default drop-and-retransmit behaviour absorbs short bursts gracefully. It is a deliberate tradeoff, not a tuning default. ## Fixing it properly Three things must line up, and fixing only one is the classic incomplete answer: 1. **Raise `net.core.somaxconn`** — it is only a ceiling. 2. **Raise the application's `listen()` backlog** — the effective queue is the *minimum* of the two, so an app that hardcodes 128 stays at 128 no matter what the sysctl says. nginx takes `backlog=` on its `listen` directive; most language runtimes expose a backlog parameter on their listen call. 3. **Make the application accept faster** — the queue is a shock absorber, not capacity. If the accept loop is blocked on slow work, on a saturated thread pool, or on a lock, a deeper queue only means more clients waiting longer before the same failure. A permanently full accept queue is a capacity problem wearing a tuning problem's clothes. ## The judgement an interviewer wants A large backlog trades visible rejection for invisible latency. Queueing connections you cannot serve within a client's timeout accomplishes nothing except making the failure slower and harder to see, and it inflates tail latency for everyone. The right size is roughly what you can drain within the burst, paired with real admission control — shedding load or returning a fast error — for the rest. Say that, and the mechanism answer becomes a design answer.

  • An operator raises net.core.somaxconn to 4096 but the stalls continue. What did they miss?
    The effective accept-queue length is the minimum of `somaxconn` and the backlog the application passed to `listen()`. If the server hardcodes 128, the sysctl change buys nothing. Raise the application's backlog too — in nginx via `backlog=` on the listen directive, elsewhere via the listen call's parameter — and restart, since the value is fixed when the socket starts listening.
  • What does enabling net.ipv4.tcp_abort_on_overflow change, and when would you want it?
    Instead of silently dropping the completing handshake packet, the kernel sends a reset, so the client fails immediately rather than stalling through SYN-ACK retransmissions. It is a good choice behind a load balancer that can retry another backend, where a fast visible failure beats an invisible delay. It is a poor choice when short bursts would otherwise drain on their own.
  • Which of the two queues does a SYN flood attack pressure, and how does Linux defend it?
    The SYN queue of half-open requests, bounded by `net.ipv4.tcp_max_syn_backlog`. The attacker never sends the final ACK, so entries accumulate. `net.ipv4.tcp_syncookies`, enabled by default on current distributions, encodes the pending state into the SYN-ACK sequence number so the kernel can stop storing requests entirely and validate the client's ACK on arrival.

saying these in an interview costs you the question

  • listen() has one queue and the backlog sizes all of it
  • Raising somaxconn alone always fixes it
  • Overflow means the client gets an immediate error
  • The application should log the dropped connections
  • A bigger backlog is always better

context