Fix Nginx "worker_connections Are Not Enough" in Front of Django

Fix the Nginx alert worker_connections are not enough while connecting to upstream for Django: count client and upstream connections, raise worker_connections and worker_rlimit_nofile, use upstream keepalive, and find slow responses that hold connections open.

Problem statement

During a traffic spike, or as WebSocket usage grows, users get 500 errors or connections hang, and the Nginx error log says:

[alert] 1234#1234: *98765 1024 worker_connections are not enough while connecting to upstream

Each Nginx worker process can hold at most worker_connections open connections at once, and the default in many distributions is 768 or 1024. A proxied request uses two: one to the client and one to Gunicorn or Uvicorn. Slow responses, long-polling, WebSockets, and many idle keep-alive clients all hold connections, so the limit is reached sooner than the number of users suggests.

How Nginx runs outEach requestclient + upstreamSlow or long-livedheld openPer worker capworker_connectionsLimit reachedalert, 500sA proxied request counts twice against the per-worker connection limit.
  1. Check current values: grep -E "worker_processes|worker_connections|worker_rlimit_nofile" /etc/nginx/nginx.conf.
  2. Raise worker_connections to 4096 or more in the events block.
  3. Raise the file descriptor limit to match: worker_rlimit_nofile 16384; (at least twice the connections).
  4. Use worker_processes auto; so each CPU core gets a worker.
  5. Find what holds connections: slow Django views, WebSockets, or long keep-alive timeouts.

Step-by-step solution

1. See how many connections are in use

Enable the stub status page on localhost:

server {
    listen 127.0.0.1:8081;
    location /nginx_status { stub_status; }
}
curl -s http://127.0.0.1:8081/nginx_status

Active connections close to worker_processes × worker_connections confirms the limit. A high Waiting count means idle keep-alive clients; a high Writing count means requests in progress, often waiting on Django.

2. Raise the limits

worker_processes auto;
worker_rlimit_nofile 16384;

events {
    worker_connections 4096;
    multi_accept on;
}

Every connection is a file descriptor, and proxied requests use two, plus files for logs and static content. worker_rlimit_nofile raises the per-worker limit without editing system limits. If Nginx runs under systemd with a lower LimitNOFILE, raise that too:

# systemctl edit nginx
[Service]
LimitNOFILE=65535

Test and reload: sudo nginx -t && sudo systemctl reload nginx.

proxied requests one server can hold (illustrative, 4 cores)768 per worker15361024 per worker20484096 per worker8192040008000Capacity is workers times connections, halved for proxyingdjango-deployment.com
Raising the limit is cheap; each idle connection uses only a few kilobytes.

3. Reuse upstream connections

Without keep-alive to the upstream, Nginx opens and closes a new connection to Gunicorn for every request, and closed connections linger in TIME_WAIT. Upstream keep-alive reduces churn:

upstream app {
    server 127.0.0.1:8000;
    keepalive 32;
}
server {
    location / {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

See Tune Nginx Upstream Keepalive for Django.

4. Find what holds connections open

Raising limits only helps if Django keeps up. If Writing stays high, requests are waiting on the app:

  • Slow views hold both connections for their full duration. Profile and fix them, or move work to Celery.
  • WebSockets hold connections for minutes or hours. Size worker_connections for peak concurrent sockets plus normal traffic, and consider a dedicated Nginx or load balancer for them.
  • Long keepalive_timeout (75 seconds by default) keeps idle browsers connected. 15 to 30 seconds is enough for most sites.

5. Limit abusive clients

A single client opening hundreds of connections can exhaust a worker. limit_conn caps connections per IP:

limit_conn_zone $binary_remote_addr zone=perip:10m;
server { limit_conn perip 50; }
connection capacity checklistworker_processes autoone per coreworker_connections 4096+events blockworker_rlimit_nofile 2x or moreand systemd LimitNOFILEUpstream keepaliveHTTP/1.1, Connection ""Shorter keepalive_timeout15 to 30 secondsRaise limits and reduce how long each connection is helddjango-deployment.com
Watch stub_status after the change to confirm active connections stay below capacity.

Explanation

Nginx uses an event loop per worker process, and each worker preallocates a connection table of size worker_connections. When a worker has no free slot, it cannot accept a client or open an upstream connection, and logs this alert. The total capacity is roughly worker_processes × worker_connections, divided by two for proxied traffic. Default values come from an era of fewer, shorter connections; modern traffic with keep-alive, HTTP/2 multiplexing, and WebSockets needs more.

Edge cases / notes

  • HTTP/2. One client connection can carry many streams, which reduces client-side connections but not upstream ones.
  • Containers. The official Nginx image sets worker_connections 1024; mount a custom nginx.conf to change it.
  • Kernel limits. Very high values may also need net.core.somaxconn and ephemeral port range tuning.

Next steps

FAQ

What is a good worker_connections value?

4096 is a common starting point for a busy single server. Size it for peak concurrent connections, counting two per proxied request.

Does raising worker_connections use much memory?

Very little. Each connection slot uses a few hundred bytes until used, and active connections a few kilobytes.

Why does it happen with WebSockets?

Each WebSocket holds a client and an upstream connection for its whole lifetime, so concurrent sockets add up quickly.

Do I also need to change ulimit?

Yes, if the process file descriptor limit is lower than what Nginx needs. worker_rlimit_nofile and systemd LimitNOFILE handle this.

Keep going