Clients report connection timeouts or resets. Your load balancer shows 502s or 504s on requests to the uWSGI backend. The uWSGI master is running, workers are alive, the stats server responds, and application logs show no errors. This is listen queue overflow: the kernel accept backlog on the uWSGI listening socket is full, and the kernel is silently dropping new connections before accept().
The listen queue is the kernel-level buffer between completed TCP handshakes and available workers. When all workers are busy and this buffer fills, the kernel silently drops incoming connections. No uWSGI log entry is written. No uWSGI error counter increments. The stats server runs in the master process and stays responsive during complete worker starvation, so health checks that query the stats endpoint still pass. Health check endpoints handled by workers may also pass if they are lightweight enough to be accepted between drops. Meanwhile, real users cannot connect.
What this means
The uWSGI listen queue is the kernel-level accept() backlog on the socket the master process binds. When a connection arrives, the kernel completes the TCP handshake and places the connection in this queue. When a worker calls accept(), it pulls the next connection and processes it. If no worker calls accept() because all workers are busy, connections accumulate in the queue. Once the queue reaches its maximum size, the kernel drops new connections.
The critical problem is observability. uWSGI’s own listen_queue stats field is broken on standard Linux. For TCP sockets, it relies on TCP_INFO behavior that varies across kernel versions. For UNIX sockets, it requires a non-standard kernel ioctl. The load field in the stats JSON is identical to listen_queue (not average latency, despite its name). The listen_queue_errors field exists in the JSON output but is dead code: it is never incremented anywhere in the uWSGI source. All three fields almost always read 0 regardless of actual backlog.
The only reliable measurements come from outside uWSGI:
ss -ltnshowsRecv-Q(current queue depth) andSend-Q(the configured backlog limit) for each listening socket.TcpExtListenOverflowsfrom/proc/net/netstatcounts the number of times the kernel dropped a connection because the accept queue was full. This counter is system-wide, not per-socket.
flowchart TD
A[New connection arrives] --> B{Worker idle?}
B -- yes --> C[accept and process]
B -- no --> D[Queue in kernel backlog]
D --> E{Backlog full?}
E -- no --> F[Wait for a worker]
F --> B
E -- yes --> G[Kernel drops connection]
G --> H[Client sees timeout or RST]
G --> I[No uWSGI log entry]
G --> J[TcpExtListenOverflows increments]When the backlog is full, the kernel does not warn uWSGI. With default kernel settings (net.ipv4.tcp_abort_on_overflow=0), the final ACK of the TCP handshake is silently dropped. The client sees a connection timeout, not an explicit error. If tcp_abort_on_overflow is set to 1, the kernel sends a RST instead and the client sees a connection reset. In both cases, uWSGI logs nothing because the connection never reached accept().
On some configurations, uWSGI may log the message *** uWSGI listen queue of socket ... full !!! (N/M) *** where N is the current queue depth and M is the configured max. This message appears only when the master process polls the queue at its internal interval and finds it full. It is not emitted for every dropped connection and may not appear at all depending on polling timing and kernel behavior.
Common causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Worker pool starvation | All workers in busy status, throughput collapsing, avg_rt rising | Worker busy ratio and per-worker uri in stats |
| Backlog too small | Recv-Q hits Send-Q ceiling during brief traffic spikes, drops clear when traffic subsides | --listen value and net.core.somaxconn |
| Stuck workers without harakiri | One or more workers permanently busy, request count frozen, no harakiri configured | Per-worker request count delta and harakiri config |
| CLOSE-WAIT socket accumulation | File descriptor count climbing, connections piling up in CLOSE-WAIT state | ss -tan state close-wait or lsof on the uWSGI process |
| Graceful reload capacity gap | Throughput drops to zero during deployment, queue fills before new workers are ready | Deployment timestamps correlated with queue depth |
Quick checks
Run these read-only commands during the incident. They are safe and non-disruptive.
# Check current accept queue depth and backlog limit (TCP socket)
# Recv-Q = current queue depth, Send-Q = configured backlog
# Replace :8000 with your uWSGI port
ss -ltn 'sport = :8000'
# For UNIX socket
ss -lxn | grep uwsgi
# Check kernel-level listen queue overflow counter (system-wide, not per-socket)
nstat -az TcpExtListenOverflows
# Check the kernel's somaxconn cap
cat /proc/sys/net/core/somaxconn
# Check worker busy ratio from uWSGI stats
uwsgi --connect-and-read 127.0.0.1:9191 | jq '([.workers[] | select(.status == "busy")] | length) as $busy | ([.workers[] | select(.pid > 0 and .status != "cheap")] | length) as $alive | if $alive > 0 then ($busy / $alive * 100) else 0 end'
# Check accepting worker count (alive, not cheaped, accepting connections)
uwsgi --connect-and-read 127.0.0.1:9191 | jq '[.workers[] | select(.pid > 0 and .status != "cheap" and .accepting == 1)] | length'
# Check what busy workers are doing (which URIs are stuck)
uwsgi --connect-and-read 127.0.0.1:9191 | jq '.workers[] | select(.status == "busy") | {id, uri, avg_rt}'
# Check for CLOSE-WAIT sockets (system-wide count; filter by PID with -p, requires root)
ss -tan state close-wait | wc -l
Do not rely on the listen_queue and listen_queue_errors fields in uWSGI stats. Both are unreliable or dead on standard Linux and almost always read 0 regardless of actual backlog. Use ss and nstat instead.
How to diagnose it
Confirm the kernel is dropping connections. Run
nstat -az TcpExtListenOverflowstwice, a few seconds apart. If the counter is rising, the kernel is actively dropping connections because an accept queue is full somewhere on the host. This counter is system-wide, so if multiple services share the host, correlate with per-socket queue depth to attribute the drops to uWSGI.Check the socket queue depth. Run
ss -ltn 'sport = :PORT'(for TCP) orss -lxn(for UNIX sockets). IfRecv-Qis non-zero and sustained, workers are not accepting fast enough. IfRecv-QequalsSend-Q, the queue is full and connections are being dropped right now.Check worker saturation. Pull the stats JSON and compute the busy ratio. If 100% of non-cheaped workers are
busy, the queue is filling because no worker can callaccept(). Check which URIs the busy workers are processing to identify the slow endpoint.Check the effective backlog limit. Compare the uWSGI
--listenvalue againstnet.core.somaxconn. The kernel silently clamps the backlog tosomaxconn. If--listenis 1024 butsomaxconnis 128, the effective backlog is 128.Check for stuck workers. If harakiri is not configured, workers that hang on blocking I/O stay stuck forever. Check whether any worker’s request count has stopped increasing while its status remains
busy. Each stuck worker permanently reduces capacity by one slot.Rule out the reload window. If the incident correlates with a deployment, the graceful reload may have drained all old workers before new workers finished loading the application. Check whether
last_spawntimestamps in the stats JSON align with the outage window.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
Socket Recv-Q (ss -ltn) | Current accept queue depth, measured externally | Sustained non-zero means workers cannot keep up |
Socket Send-Q (ss -ltn) | The effective backlog limit on the socket | Should match your --listen value (clamped by somaxconn) |
TcpExtListenOverflows (nstat) | Kernel drop counter for accept queue overflow | Any non-zero rate means connections are being dropped now |
| Worker busy ratio (uWSGI stats) | How close the pool is to exhaustion | Sustained 100% means the queue is filling |
| Accepting worker count (uWSGI stats) | Workers actively able to call accept() | Approaching zero means imminent queue fill |
| avg_rt per worker (uWSGI stats) | Application latency trend (exponential moving average, not cumulative) | Rising trend approaching harakiri means workers are about to die |
| net.core.somaxconn | Kernel cap on backlog | Lower than --listen means your backlog is silently clamped |
| CLOSE-WAIT socket count | Leaked connections consuming file descriptor slots | Growing count indicates uWSGI is not closing sockets after remote disconnect |
Fixes
Increase the backlog
Raise --listen in the uWSGI configuration and ensure net.core.somaxconn is at least as high. The default --listen is 100. The default somaxconn varies: 128 on older distributions, 4096 on kernel 5.4 and newer. Both may be too low for production traffic.
Changing --listen requires restarting uWSGI; it is a bind-time setting, not reloadable.
[uwsgi]
listen = 1024
# Set somaxconn at runtime (non-persistent across reboots; add to sysctl.conf for persistence)
sysctl -w net.core.somaxconn=1024
somaxconn changes apply only to sockets created after the change. Restart uWSGI to pick up the new value. In containers, net.core.somaxconn may not be writable without specific capabilities depending on the runtime.
A larger backlog lets the kernel buffer brief traffic spikes without dropping connections. It does not fix the underlying problem if workers are consistently saturated. It buys time.
Tradeoff: a very large backlog can mask sustained capacity problems. Connections sit in the queue for seconds, latency increases, and clients time out before a worker picks them up. Size the backlog for burst absorption, not for chronic underprovisioning.
Add worker capacity
If the busy ratio is consistently above 80% during normal traffic, the worker pool is undersized. Increase --processes or adjust the cheaper subsystem range. The degradation curve is cliff-edge: below 100% utilization, latency is roughly flat. At 100%, latency jumps non-linearly because every additional request queues in the kernel backlog.
Before adding workers, check available memory. Each worker is a full process copy. Adding workers without memory headroom triggers swapping, which makes every worker slower and can worsen the saturation.
Enable harakiri
If workers are getting stuck on blocking calls and harakiri is not configured, enable it. Without harakiri, a single hung request permanently consumes a worker slot. Over time, stuck workers accumulate until the pool is exhausted and the listen queue fills.
[uwsgi]
harakiri = 30
harakiri-verbose = true
Set the timeout to 2-3x your expected maximum legitimate request duration. The harakiri-verbose option logs additional information about the killed worker, including the blocked request URI and stack trace when available.
Harakiri does not fix the underlying hang. It kills the stuck worker and respawns it, temporarily restoring capacity. Use it as a safety net while you investigate the root cause.
Fix the slow endpoint
If all busy workers show the same URI, a single endpoint is the bottleneck. Common culprits: database queries without timeouts, external API calls without client-side timeouts, DNS resolution hanging, or lock contention. Check per-worker uri and running_time in the stats JSON.
As an emergency measure during an incident, consider blocking the slow endpoint at the load balancer to free workers for other traffic while you investigate.
Use chain reload
Standard graceful reload (SIGHUP) drains all old workers simultaneously. During the transition, capacity drops while new workers initialize and load the application. Use --chain-reload to cycle workers one at a time, maintaining partial capacity throughout the reload.
Prevention
- Monitor
TcpExtListenOverflowscontinuously. This kernel counter is the only reliable signal that connections are being dropped due to accept queue overflow. Alert on any non-zero rate of change. The counter is monotonic and system-wide; track the delta between polling intervals. - Monitor socket queue depth with
ss. Alert on sustained non-zeroRecv-Qon the uWSGI listening socket. This catches the problem before the queue fills and drops begin. - Do not trust uWSGI
listen_queue,load, orlisten_queue_errorsstats fields. All three are unreliable or dead code on standard Linux. They almost always read 0 regardless of actual backlog. - Keep
--listenandnet.core.somaxconnaligned. Ifsomaxconnis lower than--listen, the backlog is silently clamped. Check both after configuration changes and after container image rebuilds, wheresomaxconndefaults may differ from the host. - Track worker busy ratio as a leading indicator. Sustained busy ratio above 80% during normal traffic means the next traffic spike will fill the queue.
- Configure harakiri. Stuck workers without a timeout permanently reduce capacity. The absence of harakiri is itself a monitoring blind spot because
harakiri_countalways reads 0. - Do not use the stats endpoint or master PID as a health check. The master and stats server remain responsive during complete worker starvation. Health checks must go through the worker pool to detect this failure mode.
How Netdata helps
Netdata surfaces the signals that matter for this failure mode and correlates them at per-second resolution:
- TcpExtListenOverflows is collected from
/proc/net/netstat, giving you a direct kernel-level count of accept queue drops without manualnstatpolling. - Socket queue metrics (Recv-Q and Send-Q on listening sockets) are collected per-second, so you can see the queue filling before it overflows.
- uWSGI worker metrics (busy ratio, accepting worker count, avg_rt, per-worker status) are collected directly from the stats server, letting you correlate worker saturation with kernel-level drops on the same timeline.
- ML anomaly detection flags unusual changes in queue depth or worker busy ratio even when absolute values have not crossed a static threshold.
- Correlation across layers (kernel TCP counters, uWSGI stats, application latency) on a single timeline shortens the diagnostic path from “clients see connection timeouts” to “workers are saturated on this endpoint.”
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 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 thundering herd: accept() contention and the thunder-lock fix
- uWSGI worker pool starvation: the silent outage where every worker is busy






