The connection backlog is the buffer between arriving TCP connections and your uWSGI workers. It is set by two independent values that must agree: uWSGI’s --listen option and the kernel’s net.core.somaxconn. The effective backlog is the smaller of the two. If either is too small, the queue fills during brief traffic spikes or downstream slowdowns, and the kernel starts dropping connections silently.
The defaults are both low. uWSGI ships with --listen 100. Linux kernels before 5.4 default net.core.somaxconn to 128; kernels 5.4 and later raised the default to 4096. On any service handling hundreds of requests per second, a 200-millisecond downstream hiccup fills a 100-slot queue in under a second. The result is intermittent client-side connection errors (timeouts or “connection refused”) with nothing in the uWSGI logs.
What it is and why it matters
When a client connects to uWSGI’s listening socket, the kernel completes the TCP handshake and places the connection in the socket’s accept queue (the listen backlog). The connection sits there until a uWSGI worker calls accept() to pull it out and begin processing. If all workers are busy, connections accumulate in this queue. If the queue is full, the kernel drops new connections with no application-level log entry.
Two limits control the queue size:
- uWSGI
--listen(orlistenin config files): The backlog value uWSGI passes to the kernel’slisten()syscall when creating the socket. The uWSGI default is 100. net.core.somaxconn: A kernel sysctl that sets an upper bound on the backlog any socket can request. The kernel silently caps thelisten()backlog to this value.
The effective backlog is min(--listen, net.core.somaxconn). Setting --listen 1024 on a host where somaxconn is still 128 gives you an effective backlog of 128. On current uWSGI releases, if --listen exceeds somaxconn, uWSGI refuses to start entirely, printing an error and exiting.
This check applies to both TCP and UNIX domain sockets. Both values must be raised together. If you only raise --listen, uWSGI will not start.
The backlog is a cliff-edge resource. Below the limit, connections queue and eventually get served. At the limit, connections are dropped with no error counter in uWSGI and no warning. The only evidence is client-side errors or kernel-level overflow counters.
How it works
The interaction happens in three stages: socket creation, the kernel queue, and overflow handling.
Socket creation: the startup check
When uWSGI starts, the master process calls socket() followed by listen(fd, backlog) where backlog is the value from --listen. The kernel compares this to /proc/sys/net/core/somaxconn and silently truncates the backlog to the smaller value. On modern uWSGI, the master also explicitly checks somaxconn against --listen before calling listen(). If --listen > somaxconn, uWSGI prints an error and exits rather than silently running with a truncated backlog.
This means you cannot set --listen higher than somaxconn on any current uWSGI release.
flowchart TD
A["uWSGI --listen
default: 100"] --> C["Effective backlog =
min(listen, somaxconn)"]
B["net.core.somaxconn
default: 128 or 4096"] --> C
C --> D["Kernel accept queue depth limit"]
D --> E{"Queue depth < limit?"}
E -- Yes --> F["Connection queued
waits for worker accept()"]
E -- No --> G["Connection dropped
client sees timeout or RST"]
F --> H["Worker calls accept()"]
H --> I["Request processing begins"]The kernel accept queue
Once the socket is listening, every new TCP connection the kernel accepts goes into the accept queue. The queue depth rises when connections arrive faster than workers can accept() them, and falls when workers catch up. In steady state with adequate capacity, the queue depth should be zero or near-zero. Any sustained non-zero depth means workers cannot keep up.
For UNIX domain sockets, the same mechanism applies. The kernel maintains an analogous queue, and somaxconn limits it the same way.
Overflow: silent connection drops
When the queue is full, the kernel’s behavior depends on the socket type and, for TCP, the net.ipv4.tcp_abort_on_overflow sysctl:
- TCP sockets (default,
tcp_abort_on_overflow=0): The kernel silently drops the final ACK of the three-way handshake. No RST is sent. The client retransmits and eventually times out or retries the connection. This is the most common production scenario. - TCP sockets (
tcp_abort_on_overflow=1): The kernel sends RST after the final ACK. The client seesECONNREFUSEDimmediately. - UNIX domain sockets: The kernel returns
ECONNREFUSEDto the connecting process immediately.
In all cases, uWSGI has no visibility into the drop. There is no log line, no exception, and no error counter. The uWSGI listen_queue_errors stats field exists in the JSON output but is not populated.
The only way to detect overflow is at the kernel level.
Where it shows up in production
Behind nginx or a reverse proxy
When nginx sits in front of uWSGI (the most common deployment), nginx manages its own upstream connection pool. Under normal conditions, nginx opens connections to uWSGI as needed and reuses them. The uWSGI backlog sees only the connections nginx actually opens, not the full client load.
This changes under pressure. If uWSGI workers slow down (database latency, downstream API degradation), nginx’s upstream connections take longer to return. nginx opens more connections to handle incoming client requests. These connections queue in the uWSGI backlog. If the backlog is small, nginx gets connection failures and returns 502 to clients. The error appears in nginx logs (upstream failed (113: No route to host) or connect() failed (111: Connection refused)), not uWSGI logs.
Containers and Kubernetes
Docker containers using bridge networking do not inherit the host’s somaxconn. Each container gets its own network namespace, and somaxconn is initialized to the kernel’s SOMAXCONN constant (4096 on kernel 5.4+, 128 on older kernels). Changing somaxconn on the host has no effect inside containers. Containers using --network host share the host’s network namespace and thus the host’s somaxconn.
To set it per-container:
# Docker: set somaxconn at container creation (match or exceed your --listen value)
docker run --sysctl net.core.somaxconn=1024 ...
In Kubernetes, net.core.somaxconn is classified as an unsafe sysctl. You must enable it on the kubelet with --allowed-unsafe-sysctls 'net.core.somaxconn' and then set it in the pod spec under securityContext.sysctls.
Graceful reloads
During a graceful reload, old workers finish current requests while new workers start. There is a window with reduced accepting capacity. If the application has a slow import phase, this window can be long enough for the backlog to fill. A backlog of 100 connections offers almost no cushion during this period. A backlog of 1024 absorbs the burst.
The startup warning
When uWSGI starts with the default --listen 100, the startup output includes an informational line: “your server socket listen backlog is limited to 100 connections.” This is not an error. It is a hint that the default may be too small for your traffic.
Sizing the connection queue
Production recommendation
For most production deployments, --listen 1024 with net.core.somaxconn set to at least 1024 provides enough queue depth to absorb brief spikes: downstream hiccups of 200-500 milliseconds, reload windows, traffic bursts from cache misses or retry storms.
For higher-traffic services, 4096 is reasonable, especially on kernel 5.4+ where somaxconn already defaults to 4096. The practical upper limit is 65535 (the backlog value is a 16-bit integer). Values above a few thousand provide diminishing returns unless your traffic legitimately sustains thousands of queued connections, which usually indicates a capacity problem rather than a queue-sizing problem.
Setting both values
# Set the kernel sysctl (non-persistent until added to sysctl.conf or /etc/sysctl.d/)
sysctl -w net.core.somaxconn=1024
# Verify the running value
cat /proc/sys/net/core/somaxconn
# Output: 1024
# Then configure uWSGI (INI format)
# listen = 1024
# Or on the command line
uwsgi --listen 1024 ...
To make the sysctl change persistent across reboots, add it to /etc/sysctl.conf or a file under /etc/sysctl.d/.
Verifying the running value
You cannot trust the listen_queue field in the uWSGI stats JSON. The field almost always reads 0 regardless of actual backlog.
Use the ss command to measure the actual queue depth externally:
# TCP socket: check Recv-Q (current queue depth) and Send-Q (backlog limit)
ss -ltn 'sport = :8000'
# UNIX socket: same columns
ss -lxn | grep uwsgi
In the output, Recv-Q is the current number of connections waiting in the accept queue. Send-Q is the configured backlog limit. In steady state, Recv-Q should be 0. Any sustained non-zero value means workers cannot accept fast enough.
To check for overflow events at the kernel level:
# System-wide overflow and drop counters (monotonic, check rate of change)
nstat -az TcpExtListenOverflows TcpExtListenDrops
These counters are system-wide, not per-socket. On multi-service hosts, correlate them with the per-socket ss output to attribute drops to uWSGI specifically.
Tradeoffs and sizing considerations
A larger backlog is not a substitute for adequate worker capacity. The backlog absorbs transient spikes; it does not fix sustained overload. If workers cannot keep up at current traffic levels, the queue fills regardless of size, and connections are eventually dropped. The difference is whether you have seconds of cushion or fractions of a second.
| Backlog size | What it handles | When it is insufficient |
|---|---|---|
| 100 (uWSGI default) | Brief microspikes on low-traffic services | Any service behind nginx above 50 req/s, or any reload with slow startup |
| 1024 | 200-500ms downstream hiccups, reload windows, moderate bursts | Sustained overload lasting more than 1-2 seconds at high traffic |
| 4096 | Longer degradation windows, high-traffic services | Sustained overload where workers are fundamentally undersized |
The key decision is not the exact number but ensuring both values agree and are large enough for your traffic pattern. A mismatch (high --listen, low somaxconn) produces a startup failure on modern uWSGI or a silently truncated backlog on older versions.
Signals to watch in production
| Signal | Why it matters | Warning sign |
|---|---|---|
ss -ltn Recv-Q on the listening socket | Current queue depth. Non-zero means workers are not keeping up. | Sustained non-zero value, or value approaching Send-Q (the backlog limit) |
nstat -az TcpExtListenOverflows | Kernel-level overflow counter. Each increment is one or more dropped connections. | Any non-zero rate of change in production |
| Worker busy ratio | When this reaches 100%, every additional request queues in the backlog. | Sustained 80% or above indicates limited headroom |
| Average response time (avg_rt) | Rising response time means workers hold connections longer, causing the queue to fill faster. | Sustained increase above 2x baseline |
| Accepting worker count | Fewer accepting workers means less capacity to drain the queue. | Drop below expected minimum |
How Netdata helps
- Per-second collection of TCP socket queue depth lets you watch
Recv-Qclimb towardSend-Qin real time, before overflow occurs. - The
TcpExtListenOverflowsandTcpExtListenDropskernel counters are collected natively, providing direct evidence of silent connection drops that uWSGI itself cannot report. - Worker busy ratio and accepting worker count from the uWSGI stats server are correlated alongside kernel socket metrics, so you can see the relationship between worker saturation and queue depth in a single view.
- Average response time (
avg_rt) trends are tracked per worker, letting you correlate latency increases with queue buildup. - Host-level kernel parameters like
net.core.somaxconnare visible alongside runtime metrics, making it easy to confirm that the effective backlog matches your intent.
Related guides
- uWSGI all workers busy: reading the busy ratio before the queue fills
- uWSGI connection refused: clients turned away when the backlog overflows
- uWSGI listen queue full: the backlog overflow that drops connections silently
- 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-verbose: finding the blocked syscall behind a timeout
- 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






