You open the uWSGI stats server JSON during a traffic spike. listen_queue reads 0. load reads 0. listen_queue_errors reads 0. But nginx is returning 502s, clients are seeing connection refused, and all workers are busy.

The fields do not measure what you think. On standard Linux, listen_queue is broken. load is identical to listen_queue (the source code has a TODO comment admitting this). listen_queue_errors is dead code that is never incremented. All three read 0 regardless of actual socket backlog pressure.

What this means

Three fields in the uWSGI stats JSON are not trustworthy on standard Linux:

FieldWhat it claims to beWhat it actually isTrustworthy?
listen_queueSocket backlog depthTCP tcpi_unacked (unacknowledged segments) or requires a non-standard ioctlNo
loadImplied latency or load metricIdentical to listen_queue, same valueNo
listen_queue_errorsQueue overflow countDead code, never incrementedNo

If you built alerts or dashboards on any of these fields, they will never fire and will always show zero. This is not a configuration problem. It is a known limitation in how uWSGI populates these fields on Linux.

The real accept queue depth is a kernel data structure not exposed through uWSGI’s measurement path. You need external tools to see it.

Why the field is broken on Linux

uWSGI’s master process attempts to measure the listen queue depth periodically. The implementation differs between TCP and UNIX sockets, and both paths have problems on standard Linux.

flowchart LR
    A["TCP listen socket"] --> B["uWSGI master check"]
    B --> C["getsockopt TCP_INFO"]
    C --> D["tcpi_unacked
unacked segments"] D --> E["listen_queue in stats
almost always 0"] A --> F["Kernel accept queue"] F --> G["NOT exposed via
TCP_INFO"] G --> H["Invisible to uWSGI"] F --> I["ss Recv-Q
actual depth"] F --> J["nstat TcpExtListenOverflows
drop counter"]

TCP sockets: tcpi_unacked is not the accept queue

For TCP listening sockets, uWSGI calls getsockopt(fd, IPPROTO_TCP, TCP_INFO, ...) and reads tcpi_unacked from the returned struct tcp_info.

tcpi_unacked is a TCP congestion control metric. It counts unacknowledged segments in flight, not connections waiting in the accept queue. On a healthy server with no packet loss, this value is almost always 0.

The accept queue (connections that completed the TCP handshake but have not yet been pulled by accept()) is a separate kernel data structure. It is not exposed through TCP_INFO.

The max_queue field is similarly wrong. It is set to tcpi_sacked (SACKed segments), another congestion control metric unrelated to the configured listen backlog. There is a known IPv6 bug where max_queue always returns 0.

This implementation has not changed across any uWSGI 2.0.x release.

UNIX sockets: non-standard ioctl

For UNIX domain sockets, uWSGI uses a custom ioctl called SIOBKLGQ (ioctl number 0x8908) via a function that probes for it at runtime. This is a non-standard, UNBIT-specific ioctl that is not part of mainstream Linux kernels. On most systems, it is not available, and the queue measurement silently fails or returns 0.

The load field is not latency

The top-level load field in the stats JSON is set to the same value as listen_queue. Despite its name suggesting latency or system load, it is not. The uWSGI source code contains a TODO comment acknowledging that this field does not measure what its name implies.

listen_queue_errors is dead code

The listen_queue_errors field appears in the stats JSON output but no code path in the uWSGI source increments this counter. It is initialized and serialized but never updated. It will always read 0.

Quick checks

Run these to confirm whether your listen_queue field is actually broken versus your system genuinely having no queue pressure:

# Check the uWSGI stats fields
uwsgi --connect-and-read 127.0.0.1:9191 | jq '{listen_queue, load, listen_queue_errors}'

# Check the real accept queue depth (TCP)
# Recv-Q = current queue depth, Send-Q = configured backlog limit
ss -ltn 'sport = :8000'

# Check the real accept queue depth (UNIX socket)
ss -lxn 'src /run/uwsgi/app.sock'

# Check kernel-level listen overflows (system-wide)
nstat -az TcpExtListenOverflows TcpExtListenDrops

If ss shows a non-zero Recv-Q while uWSGI stats shows listen_queue: 0, the stats field is confirmed broken on your system. If nstat shows non-zero TcpExtListenOverflows while listen_queue_errors is 0, that confirms the errors field is dead too.

How to measure the real backlog

ss: current queue depth

ss reads directly from kernel netlink and shows the actual accept queue depth:

# TCP socket - check Recv-Q for depth, Send-Q for backlog limit
ss -ltn 'sport = :8000'
# Output columns: State Recv-Q Send-Q Local Address:Port Peer Address:Port
# Recv-Q > 0 means connections are waiting for accept()
# Send-Q shows the effective backlog (min of --listen and somaxconn)

# UNIX socket
ss -lxn 'src /run/uwsgi/app.sock'

Recv-Q should be 0 in steady state. Any sustained non-zero value means workers cannot accept connections fast enough. Send-Q on a LISTEN socket shows the configured backlog, which is the effective minimum of uWSGI’s --listen value and the kernel’s net.core.somaxconn.

TcpExtListenOverflows: overflow counter

When the accept queue is full and the kernel drops a connection, it increments TcpExtListenOverflows and TcpExtListenDrops:

# Current values (system-wide, not per-socket)
nstat -az TcpExtListenOverflows TcpExtListenDrops

# These are cumulative counters. Track the rate of change.
# Any non-zero rate means connections are being actively dropped.

These counters are system-wide, not per-socket. On multi-service hosts, correlate with the ss output for uWSGI’s specific socket to attribute drops correctly.

somaxconn: the silent cap

The kernel caps the listen backlog at net.core.somaxconn. If you set --listen 1024 but somaxconn is 128 (the default on many distributions), the effective backlog is 128. uWSGI does not warn about this truncation.

# Check current somaxconn
cat /proc/sys/net/core/somaxconn

# Increase it (runtime change, not persisted across reboots)
# Persist via sysctl.conf or a systemd sysctl snippet
sysctl -w net.core.somaxconn=1024

Always increase somaxconn before or alongside increasing --listen. Otherwise the larger --listen value is silently ignored.

The “listen queue full” log message

uWSGI emits a log line when it detects a full listen queue:

*** uWSGI listen queue of socket ... full !!!

This message comes from the master process periodically checking queue >= max_queue. Both values are derived from tcpi_unacked and tcpi_sacked, not from the actual accept queue. The log fires when tcpi_unacked >= tcpi_sacked, which is a TCP congestion condition, not necessarily a full accept queue.

If you see this log message, investigate TCP health (retransmissions, congestion). But the absence of this message does not mean the accept queue is not full. The real signal is TcpExtListenOverflows.

What to alert on instead

SignalSourceWhat it tells youAlert threshold
Accept queue depthss Recv-Q on the LISTEN socketConnections waiting for accept()Sustained > 0
Listen overflowsnstat TcpExtListenOverflowsConnections dropped by kernel (active outage)Any non-zero rate
Listen dropsnstat TcpExtListenDropsConnections dropped for any reasonAny non-zero rate
Worker busy ratiouWSGI stats (count status == "busy" / alive workers)Approaching capacity cliffSustained >= 80%
Accepting worker countuWSGI stats (count pid > 0 AND accepting == 1 AND status != "cheap")Available serving capacityZero for > 60s = critical

The first two signals replace the broken listen_queue and listen_queue_errors fields. They come from the kernel, not from uWSGI’s measurement path.

If you were previously alerting on listen_queue > 0 or listen_queue_errors > 0, those alerts will never fire. Replace them with ss-based queue depth monitoring and TcpExtListenOverflows rate alerting.

Prevention

  • Do not trust listen_queue, load, or listen_queue_errors in the uWSGI stats JSON on standard Linux. They are known-broken and will not improve without a source code change.
  • Monitor the accept queue externally with ss for depth and nstat for overflows. This is the only reliable signal for socket backlog pressure.
  • Set somaxconn before increasing --listen. The kernel silently truncates the backlog to somaxconn. A mismatch between --listen and somaxconn gives you a false sense of burst capacity.
  • Correlate queue depth with worker busy ratio. A growing Recv-Q with all workers busy means worker pool starvation. A growing Recv-Q with idle workers means accept contention (consider --thunder-lock).
  • Document this for your team. The most common failure mode is a new engineer building a dashboard on listen_queue, seeing it always at zero, and assuming the queue is healthy during an incident.

How Netdata helps

Netdata’s uWSGI collector reads the stats server JSON and surfaces worker-level metrics: busy ratio, accepting worker count, harakiri rate, avg_rt, exceptions, respawn rate, and per-worker RSS. For the broken listen_queue fields, Netdata’s Linux network monitoring provides the signals that fill the gap:

  • TCP accept queue depth from netlink, showing per-socket Recv-Q at per-second resolution.
  • TcpExtListenOverflows and TcpExtListenDrops from kernel counters, tracked as rates for overflow detection.
  • Worker busy ratio and accepting worker count from the uWSGI stats server, correlated against queue depth to distinguish starvation from accept contention.
  • Harakiri rate and avg_rt trends that precede queue buildup, giving earlier warning than the queue itself.

Seeing the kernel-level queue signals and uWSGI worker metrics in a single timeline is what distinguishes “listen_queue is zero, everything looks fine” from “Recv-Q is growing, all workers are busy, harakiri count just started rising.”