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.
- Check current values:
grep -E "worker_processes|worker_connections|worker_rlimit_nofile" /etc/nginx/nginx.conf. - Raise
worker_connectionsto 4096 or more in theeventsblock. - Raise the file descriptor limit to match:
worker_rlimit_nofile 16384;(at least twice the connections). - Use
worker_processes auto;so each CPU core gets a worker. - 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.
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_connectionsfor 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; }
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 customnginx.confto change it. - Kernel limits. Very high values may also need
net.core.somaxconnand ephemeral port range tuning.
Next steps
- Reduce upstream churn with Tune Nginx Upstream Keepalive for Django.
- For descriptor limits in Django itself, see Fix Django Too Many Open Files.
- Prepare for peaks with the Django Traffic Spike Readiness Checklist.
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.