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:
| Field | What it claims to be | What it actually is | Trustworthy? |
|---|---|---|---|
listen_queue | Socket backlog depth | TCP tcpi_unacked (unacknowledged segments) or requires a non-standard ioctl | No |
load | Implied latency or load metric | Identical to listen_queue, same value | No |
listen_queue_errors | Queue overflow count | Dead code, never incremented | No |
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
| Signal | Source | What it tells you | Alert threshold |
|---|---|---|---|
| Accept queue depth | ss Recv-Q on the LISTEN socket | Connections waiting for accept() | Sustained > 0 |
| Listen overflows | nstat TcpExtListenOverflows | Connections dropped by kernel (active outage) | Any non-zero rate |
| Listen drops | nstat TcpExtListenDrops | Connections dropped for any reason | Any non-zero rate |
| Worker busy ratio | uWSGI stats (count status == "busy" / alive workers) | Approaching capacity cliff | Sustained >= 80% |
| Accepting worker count | uWSGI stats (count pid > 0 AND accepting == 1 AND status != "cheap") | Available serving capacity | Zero 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, orlisten_queue_errorsin 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
ssfor depth andnstatfor overflows. This is the only reliable signal for socket backlog pressure. - Set
somaxconnbefore increasing--listen. The kernel silently truncates the backlog tosomaxconn. A mismatch between--listenandsomaxconngives you a false sense of burst capacity. - Correlate queue depth with worker busy ratio. A growing
Recv-Qwith all workers busy means worker pool starvation. A growingRecv-Qwith 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-Qat 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.”
Related guides
- uWSGI all workers busy: reading the busy ratio before the queue fills
- uWSGI harakiri death spiral: workers killed and respawned while throughput collapses
- uWSGI harakiri not configured: stuck workers with no timeout and no recovery
- uWSGI harakiri timeout: setting it against request duration and nginx timeouts
- uWSGI HARAKIRI ON WORKER: requests killed for exceeding the timeout
- How uWSGI actually works in production: a mental model for operators
- uWSGI master process dead: total outage while the PID file lingers
- uWSGI monitoring checklist: the signals every production app server needs
- uWSGI monitoring maturity model: from survival to expert
- uWSGI worker stuck in busy: a hung request that never returns
- uWSGI thundering herd: accept() contention and the thunder-lock fix
- uWSGI worker pool starvation: the silent outage where every worker is busy






