Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for NGINX By James Joyner IV · · 9 min read

NGINX Error Guide: '(24: Too many open files)' File Descriptor Exhaustion

Fix NGINX 24 Too many open files by raising worker_rlimit_nofile and the systemd LimitNOFILE override that caps the file descriptor limit.

  • #nginx
  • #troubleshooting
  • #errors
  • #limits

Exact Error Message

When NGINX runs out of file descriptors, the error surfaces in several closely related forms in your error.log. You will typically see one or more of these lines under load:

2026/06/27 13:22:09 [crit] 2841#2841: accept4() failed (24: Too many open files)
2026/06/27 13:22:09 [crit] 2841#2841: *50231 open() "/var/www/html/assets/app.js" failed (24: Too many open files), client: 203.0.113.44, server: example.com, request: "GET /assets/app.js HTTP/1.1", host: "example.com"
2026/06/27 13:22:10 [alert] 2841#2841: *50244 socket() failed (24: Too many open files) while connecting to upstream, client: 203.0.113.91, server: example.com, request: "GET /api/orders HTTP/1.1", upstream: "http://10.0.0.7:8080/api/orders", host: "example.com"

The numeric code 24 is the POSIX errno EMFILE (“too many open files”). It is reported by accept4() when NGINX cannot accept new client connections, by open() when it cannot read a static file from disk, and by socket() when it cannot create a socket to a proxied upstream. All three share the same root cause: the NGINX worker process has hit its per-process limit on open file descriptors.

What the Error Means

On Linux, every open file, socket, pipe, and connection is represented by a file descriptor (fd). The kernel enforces a per-process ceiling on the number of fds a process may hold open at once, called RLIMIT_NOFILE. When an NGINX worker tries to open one more fd than its limit allows, the syscall fails with EMFILE and NGINX logs the (24: Too many open files) message.

Each NGINX worker is a separate process, so the limit is per worker, not for NGINX as a whole. A single client request can consume multiple descriptors: one for the client connection, one (or more) for the upstream socket when proxying, plus descriptors for any static files served, cached open files, and log files. Under high concurrency a busy worker can easily need tens of thousands of descriptors.

RLIMIT_NOFILE has two values: a soft limit (the value currently enforced) and a hard limit (the ceiling the soft limit may be raised to without privileges). NGINX’s worker_rlimit_nofile directive raises the soft limit at startup, but it can never exceed the hard limit the process inherited from its parent. On modern systemd-managed hosts, that hard limit is set by LimitNOFILE= in the service unit, which is why fixing this error almost always involves both an NGINX directive and a systemd override.

Common Causes

  • Default soft limit too low. A traditional default of 1024 open files per process is far too small for a reverse proxy handling thousands of concurrent connections.
  • worker_connections set high but the fd limit not raised. Setting worker_connections 16384; promises up to 16k connections per worker, but each needs descriptors. If the fd limit stays at 1024, NGINX fails long before reaching that number.
  • Proxying to upstreams. Each proxied request opens an additional socket to the backend, roughly doubling fd usage per active connection.
  • systemd LimitNOFILE caps everything. Even with a generous worker_rlimit_nofile, the systemd unit’s hard limit overrides it. NGINX cannot raise its soft limit above the inherited hard limit.
  • Genuine fd leak or traffic spike. A misbehaving upstream, slow clients (Slowloris-style), or keepalive connections piling up can consume descriptors faster than they are released.

How to Reproduce the Error

You can reproduce the condition safely in a test environment. Lower the limit deliberately and drive concurrent traffic:

# Inspect the current limit NGINX is running with
cat /proc/$(pgrep -o nginx)/limits | grep "Max open files"

Then set an artificially low worker_rlimit_nofile 64; in nginx.conf, raise worker_connections, restart NGINX, and hammer it with a load generator (for example wrk -c 200 -t 4 http://localhost/). Within seconds the error.log fills with (24: Too many open files) as workers exhaust their 64-descriptor budget. This confirms the relationship between concurrency and the descriptor ceiling.

Diagnostic Commands

Start by confirming the configuration is valid and reading the limits NGINX is actually enforcing. All commands below are read-only:

# Validate config syntax
sudo nginx -t

# Dump the full effective config and check the relevant directives
sudo nginx -T | grep -E "worker_rlimit_nofile|worker_connections|worker_processes"

# The limit the running master/worker actually has (soft and hard)
cat /proc/$(pgrep -o nginx)/limits | grep "Max open files"

# Same data via prlimit for a specific PID
prlimit --pid $(pgrep -o nginx) --nofile

# What systemd will hand the service on next (re)start
systemctl show nginx -p LimitNOFILE

# Your shell's own limit (for context, not NGINX's)
ulimit -n

# How many descriptors a given worker currently holds open
ls /proc/<worker-pid>/fd | wc -l

# Socket summary across the host
ss -s

# Recent service errors, including startup limit warnings
journalctl -u nginx --since "1 hour ago"

If cat /proc/<pid>/limits shows Max open files at 1024 while worker_connections is set to something much larger, you have found the mismatch. Comparing ls /proc/<pid>/fd | wc -l against the limit tells you how close a worker is to exhaustion in real time.

Step-by-Step Resolution

The fix has two layers: raise the systemd hard limit, then raise the NGINX soft limit to use it.

1. Raise the systemd hard limit with a drop-in. Create a drop-in directory and file at /etc/systemd/system/nginx.service.d/limits.conf containing a [Service] section with LimitNOFILE=65535 (or higher, e.g. 262144 for very high-traffic hosts). Using a drop-in rather than editing the shipped unit means your change survives package upgrades. After saving it, reload systemd so it re-reads the unit:

sudo systemctl daemon-reload

2. Raise the NGINX soft limit. In the main context of /etc/nginx/nginx.conf (top level, not inside http, server, or events), set:

worker_rlimit_nofile 65535;

This value must be less than or equal to the systemd LimitNOFILE hard limit from step 1 — NGINX cannot raise its soft limit above the inherited hard ceiling.

3. Right-size worker_connections. In the events block, ensure worker_connections is sane relative to the fd budget. A rough rule for a reverse proxy is worker_rlimit_nofile >= 2 * worker_connections to leave room for upstream sockets, log files, and cached open files.

4. Validate and apply. Because raising worker_rlimit_nofile and LimitNOFILE changes process-level limits, a plain reload is not enough — the master process inherits its limits at start time. You must perform a full restart:

sudo nginx -t && sudo systemctl restart nginx

A reload (sudo systemctl reload nginx) is sufficient only for ordinary config changes that do not touch worker_rlimit_nofile or the systemd limits. After restarting, re-run cat /proc/$(pgrep -o nginx)/limits | grep "Max open files" to confirm the new value is live.

Prevention and Best Practices

  • Set both limits together. Whenever you raise worker_connections, raise worker_rlimit_nofile and LimitNOFILE in lockstep so they never drift apart.
  • Use generous but bounded values. 65535 is a reasonable default for busy proxies; push to 262144 only for genuinely high-concurrency edge nodes, and confirm the kernel’s fs.file-max is high enough to back it.
  • Prefer drop-ins over editing shipped units so limit changes persist through apt/yum upgrades.
  • Monitor descriptor usage proactively. Track open fds per worker and alert before the ceiling is reached, rather than discovering it from a flood of (24) errors during an outage. You can wire descriptor and connection metrics into your alerting stack via the monitoring dashboard.
  • Tame keepalive and Slowloris exposure. Reasonable keepalive_timeout, client_body_timeout, and upstream keepalive pool sizes keep descriptors from accumulating on idle or slow connections.
  • Verify after every deploy that systemctl show nginx -p LimitNOFILE still reflects your intended value; package updates occasionally reset unit defaults.
  • worker_connections are not enough — a different ceiling. This means a worker hit its worker_connections cap, not the fd limit. Raise worker_connections (and the fd limits to match).
  • (98: Address already in use) — a bind/port conflict, unrelated to descriptors, usually from a stale process or a second service on the same port.
  • upstream timed out (110: Connection timed out) — a slow or unreachable backend; descriptor exhaustion can contribute by delaying socket creation, but the primary cause is upstream latency.
  • accept() failed (24: Too many open files) — the legacy accept() variant of the same error on older kernels; the fix is identical.

More NGINX troubleshooting guides are collected under the NGINX category.

Frequently Asked Questions

Why does raising worker_rlimit_nofile alone not fix the error?

Because worker_rlimit_nofile only raises the soft limit, and it can never exceed the hard limit NGINX inherited at startup. On systemd hosts that hard limit comes from LimitNOFILE= in the service unit. If LimitNOFILE is still 1024, NGINX silently caps your directive at 1024. You must raise LimitNOFILE via a drop-in and daemon-reload first.

Do I need to restart NGINX or is a reload enough?

A reload is not enough for limit changes. The master process reads worker_rlimit_nofile and inherits RLIMIT_NOFILE from systemd only at start time. After changing either, run sudo systemctl restart nginx. Use reload only for ordinary config edits that do not touch process limits.

How high should I set the limit?

Size it to roughly twice your peak per-worker concurrency to cover client connections, upstream sockets, open files, and logs. 65535 suits most busy proxies; 262144 fits high-traffic edge nodes. Make sure the kernel-wide fs.file-max is comfortably above the sum of all workers’ limits.

Does ulimit -n in my shell affect NGINX?

No. ulimit -n shows your interactive shell’s limit, which is irrelevant to a systemd-managed NGINX service. NGINX gets its hard limit from LimitNOFILE in the unit file, not from your login shell. Always check cat /proc/$(pgrep -o nginx)/limits to see the limit NGINX actually runs with.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.