Troubleshooting

What should worker_connections be on an nginx reverse proxy?

Every proxied request holds two connections in one worker, idle upstream pools hold more, and the open-files limit caps it all. The sizing formula, worked through.

· 15 min read · How we verify this

Key points

  • On a reverse proxy, each request in flight holds two slots in one worker: the client connection and the upstream connection. Idle upstream keep-alive connections, listening sockets and WebSockets count too, so a worker at worker_connections 1024 carries at most about 500 concurrent proxied requests.
  • The compiled default is 512. Ubuntu and Debian ship 768 in their packaged nginx.conf; the nginx.org packages, the official Docker image and nginx's own sample config ship 1024. No worker_connections line in nginx -T means 512.
  • The real ceiling is the smaller of worker_connections and the worker's open-files limit. Under systemd that soft limit is 1024 unless something raises it, so worker_connections 4096 alone still stops at about 1024. worker_rlimit_nofile raises it.
  • worker_connections are not enough, reusing connections is a warning that nginx closed idle client keep-alive connections to free slots. Without reusing connections it is an alert: the connection or request failed, and an upstream connect that fails this way returns 500, not 502.

On an nginx reverse proxy, set worker_connections to at least twice the number of requests each worker will have in flight to upstreams at peak, plus its idle client keep-alive connections, plus its idle upstream keep-alive connections, plus a few slots for listening sockets, and then roughly double that for headroom. For a busy single-host proxy that usually lands between 2048 and 4096. The factor of two is the part most advice leaves out: the nginx documentation says the number "includes all connections (e.g. connections with proxied servers, among others), not only connections with clients", so every proxied request occupies a client slot and an upstream slot in the same worker. The second half of the answer is that worker_connections is only a request. The worker cannot hold more connections than its open-files limit (RLIMIT_NOFILE) allows, so raise that with worker_rlimit_nofile in the same change.

worker_connections are not enough means one worker ran out of those slots. With , reusing connections on the end it is a warning and nginx recovered by closing idle keep-alive clients. Without it, something failed.

The formula, per worker#

Everything is per worker process, because each worker has its own fixed pool of connection slots and nothing is shared between them.

TermSlots per itemCan nginx reclaim it under pressure?
Proxied request in flight (client connection + upstream connection)2No
WebSocket or SSE stream, for its whole lifetime2No
Idle client keep-alive connection, or a client that has connected but not yet sent a request1Yes: this is what reusing connections closes
Idle upstream keep-alive connection in a pool1No
Listening socket (each listen address, IPv4 and IPv6 counted separately)1 eachNo
Channel socket between the worker and the master1No
Request answered by nginx itself (static file, return, cache hit)1No

Written out:

text
worker_connections >= 2 x (proxied requests in flight + WebSocket/SSE streams) / workers
                    + idle client keep-alive connections / workers
                    + upstream groups x keepalive
                    + listening sockets + 1
then double it, and set worker_rlimit_nofile to about twice worker_connections

The upstream pool term is not divided by the number of workers. The upstream module's keepalive value is "the maximum number of idle keepalive connections to upstream servers that are preserved in the cache of each worker process", so each worker can hold a full pool for each upstream group. Since nginx 1.29.7 that pool exists without any configuration: the default is keepalive 32 local, so a stock configuration with four upstream blocks can park up to 128 idle upstream connections in every worker. On older builds the term is zero unless you wrote keepalive N yourself. Keep-alive and upstream connection pooling covers why the pool is worth those slots.

The final doubling covers two things: workers do not share load evenly (the ticket discussed below is one worker filling up while the others sat half empty), and bursts go above the peak you measured.

Worked example: an 8-core proxy#

An 8-core host with worker_processes auto; runs 8 workers. At peak it holds 4,000 client connections: 1,600 have a request in flight to a backend, 400 are WebSockets, and 2,000 are idle keep-alive. It runs nginx 1.30.x with four upstream blocks left at the default pool size, and listens on ports 80 and 443 over IPv4 and IPv6.

TermTotalPer workerSlots per worker
Proxied requests in flight1,600200400
WebSockets40050100
Idle client keep-alive2,000250250
Upstream pools: 4 groups x 32n/a128128
Listening sockets (4) + channel (1)n/a55
Total at an even split883

883 slots per worker with a perfectly even spread. Doubled, that is about 1,770, so worker_connections 2048. Two comparisons make the point. The Debian and Ubuntu packaged value of 768 is already short at an even split, before any burst. And the 633 slots that nginx cannot reclaim (everything except idle clients) are already past that 768 on their own, so on that box the warning would turn into failures, not just into closed keep-alive connections.

The configuration that follows from it:

nginx
worker_processes auto;
worker_rlimit_nofile 4096;     # raises RLIMIT_NOFILE for workers; about 2 x worker_connections

events {
    worker_connections 2048;
}

worker_rlimit_nofile is set to twice worker_connections because connections are not the only descriptors a worker holds: log files, files being served, and the temporary files nginx writes when proxy buffering spills a large upstream response to disk all take descriptors too, and worker_connections does not count them.

Which default you actually have#

Three different numbers circulate as "the default", and all three are real. They come from different layers.

Where the value comes fromworker_connectionsApplies when
Compiled-in default (nginx core module documentation)512The events block has no worker_connections line
nginx's own sample conf/nginx.conf in the source tree1024You built from source and kept the sample file
nginx.org packages (pkg-oss) and the official Docker image1024Installing from nginx.org, or running nginx:1.30-alpine and similar tags
Debian 12, Debian 13 and unstable packaged nginx.conf768apt install nginx on Debian
Ubuntu packaged nginx.conf (merged from Debian)768apt install nginx on Ubuntu

The 768 that appears in forum threads is the Debian packaging value, and Ubuntu inherits it. It is neither nginx's default nor a recommendation for proxies. Check the running configuration rather than the file you think is loaded:

bash
nginx -T 2>/dev/null | grep -E 'worker_(connections|processes|rlimit_nofile)'

If no worker_connections line comes back, the value is 512.

Why "set 2048 or 4096" is half an answer#

The usual blog advice is to raise the value to 2048 or 4096, often alongside the formula max clients = worker_processes x worker_connections. Both pieces come from a web server serving files, where one client is one connection. On a proxy, that formula overstates capacity by at least a factor of two, and by more once upstream pools are counted. 8 workers at 2048 is not 16,384 concurrent proxied clients; it is under 8,192, minus 128 pooled upstream connections per worker in the example above.

The bigger problem is that 4096 frequently does nothing. When nginx runs under systemd, which is how the distribution and nginx.org packages start it, the worker inherits a soft open-files limit of 1024. The systemd-system.conf manual says DefaultLimitNOFILE= "defaults to 1024:" followed by a high hard limit, and the systemd.exec manual gives that hard limit: "Nowadays, the hard limit defaults to 524288". Neither the nginx.org nginx.service nor Ubuntu's sets LimitNOFILE=. The nginx documentation is explicit that "the actual number of simultaneous connections cannot exceed the current limit on the maximum number of open files". So a worker configured for 4096 connections fails at roughly 1024 descriptors, with a different error from the one you were trying to fix.

Debian's 768 fits under that 1024 ceiling. Any value much above it needs the open-files limit raised in the same change.

The open-files ceiling, and the errors it produces#

nginx checks the mismatch when it reads the configuration, and says so on stderr and in the error log:

text
[warn] 1#1: 512 worker_connections exceed open file resource limit: 32

That line came from a test container started with --ulimit nofile=32:8192. It is only a warning: nginx starts anyway, and the limit bites under load. Once the worker runs out of descriptors, the errors are errno 24. On a glibc system they read:

text
[crit] accept4() failed (24: Too many open files)
[alert] socket() failed (24: Too many open files) while connecting to upstream, client: ..., upstream: "http://10.201.4.2:8000/"

The first is a new client that could not be accepted; nginx briefly stops accepting on that worker. The second is a client already accepted whose upstream socket could not be created, and in testing that request got a 500. The Alpine-based official image words errno 24 as No file descriptors available instead (a musl versus glibc difference), so search for (24: rather than for the text.

There are three ways to raise the limit, and they differ in who does it:

MethodWhat it changesNotes
worker_rlimit_nofile N; in the main contextThe workers' RLIMIT_NOFILE, soft and hard, set by nginx itselfThe documented nginx way. In testing the worker's limits read 4096 4096 after worker_rlimit_nofile 4096. Works under systemd because the hard limit (524288) is far above any sensible value
LimitNOFILE= in a systemd drop-in (systemctl edit nginx)The limit the whole service starts withsystemd's own manual marks LimitNOFILE= "Do not use" and says applications "should increase their soft limit to the hard limit on their own", which is exactly what worker_rlimit_nofile does
--ulimit nofile=soft:hard (Docker) or ulimits: (Compose)The container's limitsDocker says that without a value they "are inherited from the default ulimits set on the daemon". On Docker Engine 28.1.1 here, containers started with 1048576 for both

What the two warning lines actually mean#

Both strings come from the same function in src/core/ngx_connection.c, which hands out connection slots. The source makes the difference precise.

N worker_connections are not enough, reusing connections is logged at warn. nginx runs it when a worker's free slots fall to one sixteenth of the pool or below and at least one connection is marked reusable. Reusable connections are client connections that hold no request: idle keep-alive connections, connections waiting for their first request, and connections in lingering close. nginx closes some of them (at most 32 at a time, and at most one log line per second) and carries on. Idle upstream keep-alive connections are not on that list.

N worker_connections are not enough, with nothing after it, is logged at alert and means there was no free slot at all. What the client sees depends on where it happened:

  • At accept: the new client connection is closed straight away. The client sees a reset or an empty reply, and nothing appears in the access log.
  • While connecting to upstream: the line carries while connecting to upstream, and the request that already had a client slot fails with 500 Internal Server Error. It is not a 502, which matters if you are working from the 502 vs 503 vs 504 decision table: this is the one upstream-connection failure that shows up as a 500.

A test with nginx 1.27.5, one worker, worker_connections 20 and a backend that took 5 seconds per request showed all of it:

TestResult
9 concurrent proxied requests9 x 200. Two slots were already taken by the listening socket and the channel, leaving 18, which is 9 pairs
10 concurrent, worker_connections 219 x 200, 1 x 500, [alert] ... 21 worker_connections are not enough while connecting to upstream
6 idle keep-alive clients, then 9 slow requests9 x 200, one [warn] ... reusing connections, and all 6 idle clients disconnected
8 idle upstream keep-alive connections from an earlier burst, then 9 slow requests to another upstream5 x 200 and 4 failures, [alert] only: nginx did not close the idle upstream connections to make room

The last row repeated on nginx 1.30.5 with a stock upstream block containing no keepalive line at all: the 1.29.7 default pooled the 8 idle connections and capacity dropped the same way. On current builds the upstream pool term in the formula applies even when the configuration never mentions keep-alive.

The trac #2285 case: the warning with "plenty" of headroom#

Ticket #2285 is the case that confuses people most. The reporter ran nginx 1.20.2 on a 12-core Linux host with worker_processes auto, worker_connections 10000 and worker_rlimit_nofile 120000, and got:

text
[warn] 114001#114001: 10000 worker_connections are not enough, reusing connections

stub_status showed about 6,190 active connections, a long way under 12 x 10,000. Maxim Dounin's reply explained two things. First, stub_status counts client connections only, so upstream connections and listening sockets are invisible in it. Second, the real cause was poor distribution of client connections between workers: with EPOLLEXCLUSIVE, which nginx uses on Linux by default, one worker could fill its 10,000 slots while the others stayed underused. The suggested workarounds were accept_mutex on;, listen ... reuseport, or building with -DNGX_HAVE_EPOLLEXCLUSIVE=0. The ticket was closed as fixed, and nginx 1.21.6 (25 January 2022) lists the bugfix "when using EPOLLEXCLUSIVE on Linux client connections were unevenly distributed among worker processes".

The lessons outlast the bug: the limit and the warning are per worker, so a total across workers tells you little, and stub_status cannot show the number nginx is actually counting.

Measure what a worker actually holds#

Because stub_status leaves out upstream connections, count descriptors per worker directly:

bash
for p in $(pgrep -f 'nginx: worker'); do
  printf '%s fds=%s limit=%s\n' "$p" "$(ls /proc/$p/fd | wc -l)" \
    "$(awk '/open files/ {print $4}' /proc/$p/limits)"
done

fds includes log files, so it slightly overstates connection use, which is the safe direction. Two things to look for: a worker far above the others (distribution, as in #2285), and an open files limit at 1024 when you configured more (systemd, with no worker_rlimit_nofile). Run it at peak, not after the alert, and compare the highest worker, not the average, with worker_connections.

Long-lived connections change the arithmetic#

A short proxied request holds its two slots for milliseconds; a WebSocket or server-sent events stream holds them for as long as the client stays connected, which can be hours. Five thousand open WebSockets spread over 8 workers is 1,250 slots per worker before any ordinary traffic, which is past the 1024 default on its own. Size for the peak count of open sockets, not for request rate. WebSockets through a reverse proxy covers the timeouts that decide how long those slots stay held.

HTTP/2 from clients has the opposite effect on the client side only. One HTTP/2 connection is one client slot however many streams it carries, but with HTTP/1.1 to the upstream, each concurrently proxied stream needs its own upstream connection. A browser holding one connection with 20 streams in flight costs 21 slots, not 2. HTTP/2 and HTTP/3 through proxies explains why the two legs differ. The same two-per-session arithmetic applies to the stream module: a proxied TCP session holds a client connection and an upstream connection.

Failure modes#

reusing connections in the log, no errors anywhere. nginx is closing idle keep-alive clients, which reconnect without complaint, but the worker is within one sixteenth of its pool. Raise worker_connections and check RLIMIT_NOFILE before the alert form appears.

Sporadic 500s with worker_connections are not enough while connecting to upstream. Slots ran out between accepting the client and connecting upstream. Count idle upstream pool connections: on 1.29.7 and later they exist by default, one pool per upstream block per worker.

Resets or empty replies, nothing in the access log. The accept-side alert, or accept4() failed (24: ...). Both happen before nginx has a request to log, so the error log is the only record. The proxy debugging playbook covers telling this apart from a load balancer in front dropping the connection.

You raised worker_connections and nothing changed. The startup log shows worker_connections exceed open file resource limit. Add worker_rlimit_nofile, then reload.

setrlimit(RLIMIT_NOFILE, N) failed (1: Operation not permitted). A container with a hard limit below N. Raise the hard limit with --ulimit or Compose ulimits:.

One worker saturated, the rest idle. Uneven accept distribution, as in #2285. On builds before 1.21.6, use listen ... reuseport or accept_mutex on.

Where this answer stops applying#

The numbers and log lines here are from Linux with the epoll event method, nginx 1.27.5 and 1.30.5 in the official Alpine images, and Docker Engine 28.1.1. Other event methods and operating systems have their own descriptor limits, and this page does not cover them. The systemd figures are documented defaults that an administrator can change system-wide with DefaultLimitNOFILE=, and container limits come from the runtime, which is why the measuring loop above reads the limit from /proc instead of assuming it. For the rest of the proxy configuration these slots serve, see nginx as a reverse proxy.

Frequently asked questions#

What is the default worker_connections in nginx?#

512, when the configuration does not set it. Packaged configuration files usually do set it: Debian and Ubuntu ship 768, and the nginx.org packages and the official Docker image ship 1024. Run nginx -T and look for the line; if it is absent, the value is 512.

What does "worker_connections are not enough, reusing connections" mean?#

A worker had almost no free connection slots left, so nginx closed some idle client keep-alive connections to free them. It is a warning, and requests kept being served. Without the reusing connections suffix the message is an alert, and a connection or request failed.

How many clients can nginx handle with worker_connections 1024?#

As a reverse proxy, at most about 500 concurrent proxied requests per worker, because each one uses a client connection and an upstream connection. Listening sockets and idle upstream keep-alive connections reduce that further. Multiply by the number of workers only if connections are spread evenly between them.

Should worker_rlimit_nofile be larger than worker_connections?#

Yes, about twice as large is a common choice. Each connection is one file descriptor, and workers also hold descriptors for log files, served files and temporary buffer files that worker_connections does not count.

Why does nginx return 500 when worker_connections runs out?#

Because the failure happens inside nginx, after it has accepted the client and before it has an upstream connection. nginx finalises that request with 500 Internal Server Error. When the shortage hits at accept time instead, the client gets no response at all.

Primary sources#

Every normative claim on this page is checked against the specification or the vendor documentation listed here. Where behaviour is version dependent, the version is named in the text.

  1. nginx core module: worker_connections, worker_rlimit_nofile, accept_mutex
  2. nginx ngx_http_upstream_module: keepalive (default keepalive 32 local since 1.29.7)
  3. nginx CHANGES (1.21.6, 1.29.7)
  4. nginx trac ticket #2285: worker_connections are not enough, reusing connections
  5. nginx source: src/core/ngx_connection.c, src/event/ngx_event.c, conf/nginx.conf
  6. Ubuntu nginx packaging: debian/conf/nginx.conf and nginx.service
  7. Debian nginx package sources (bookworm, trixie, unstable)
  8. nginx.org packaging (pkg-oss): nginx.conf and nginx.service
  9. systemd.exec(5) LimitNOFILE and systemd-system.conf(5) DefaultLimitNOFILE, man page sources
  10. Docker CLI reference: docker container run --ulimit

Found something wrong, or behaviour that differs on your version? Report it with the version number and a primary source. Anything substantive is fixed in the page and logged on the corrections page. See editorial standards for how pages are researched, sourced and reviewed.

More in troubleshooting proxies#