Fix "Too Many Open Files" on Linux
Your server logs fill up with EMFILE: too many open files and then the process just stops accepting new connections. Restarting it "fixes" things for an hour, then it happens again. This is one of the most common production incidents on Linux, and it almost never means your disk is full of files — it means a process has hit its limit on how many file descriptors (sockets, open files, pipes) it's allowed to hold open at once.
This guide covers why the "too many open files" error happens, how Linux's file descriptor limits actually work, and the exact commands to raise them properly — not just a one-off ulimit command that resets on the next reboot.
What "too many open files" actually means
Every open file, network socket, and pipe a process uses counts against a per-process file descriptor limit. On Linux this shows up as one of two errors:
EMFILE— the calling process itself has hit its own open-file limitENFILE— the whole system has hit its global open-file limit (rarer, usually only on very busy multi-tenant hosts)
Most of the time you'll see EMFILE, and it's almost always one of these causes:
- A Node.js, PHP-FPM, or Java process is leaking file descriptors — opening database connections, HTTP keep-alive sockets, or file streams without closing them
- Nginx or a reverse proxy is holding far more concurrent connections than the default limit allows
- A default
ulimit -nof 1024 (the standard on most distros) is simply too low for a production web server under real traffic
Checking your current limits
Before changing anything, check what the running process is actually allowed and what it's actually using:
# soft limit for your current shell
ulimit -Sn
# hard limit (the ceiling the soft limit can be raised to)
ulimit -Hn
# what a *running* process is actually allowed
cat /proc/<PID>/limits | grep "Max open files"
# how many file descriptors that process currently has open
ls /proc/<PID>/fd | wc -l
If the count from ls /proc/<PID>/fd | wc -l keeps climbing over time without leveling off, that's a leak in your application code, not a limits problem — raising the ceiling will only delay the crash, not prevent it. Fix the leak first, then size the limits properly.
Step-by-step: raising file descriptor limits correctly
1. Raise the system-wide ceiling
fs.file-max caps how many open files the entire system can have, across every process combined:
sudo sysctl -w fs.file-max=2097152
echo "fs.file-max = 2097152" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
2. Raise per-user limits
This is the piece people usually skip, and it's why a ulimit -n 65535 typed into a terminal "doesn't work" — it only applies to that one shell session. Set it permanently in /etc/security/limits.conf:
# /etc/security/limits.conf
www-data soft nofile 65535
www-data hard nofile 65535
root soft nofile 65535
root hard nofile 65535
Replace www-data with whatever user actually runs your app (node, deploy, your PHP-FPM pool user, etc.). This requires a new login session to take effect — a running SSH session or an already-started service won't pick it up automatically.
3. Fix the limit for systemd-managed services specifically
If your app runs as a systemd service (which is the case for most production Node.js, Laravel queue workers, and Nginx setups), /etc/security/limits.conf is often ignored entirely, because systemd services don't go through a login shell (PAM). You need to set the limit directly on the service:
sudo systemctl edit your-app.service
Add this in the editor that opens (it creates an override file, so you never touch the original unit file):
[Service]
LimitNOFILE=65535
Then apply it:
sudo systemctl daemon-reload
sudo systemctl restart your-app.service
Verify it actually took effect:
systemctl show your-app.service -p LimitNOFILE
Common mistakes that make this "fix" not actually work
- Raising
ulimit -nin a terminal and expecting a systemd service to inherit it — it won't. systemd services needLimitNOFILEset on the unit itself, per the step above. - Forgetting to restart the service after editing limits — the new ceiling only applies to processes started after the change, not ones already running.
- Only raising the soft limit — some runtimes (older Node.js versions, some JVM configurations) read the hard limit at startup and refuse to exceed it even if you bump the soft limit later.
- Treating a rising limit as the fix instead of the symptom — if
lsof -p <PID>shows thousands of sockets stuck inCLOSE_WAIT, that's a code-level leak (unclosed database connections or HTTP clients), and no ceiling is high enough to outrun it forever.
Monitoring to catch it before it takes the app down
A simple watch loop is enough to catch a leak trending toward the ceiling before it becomes an outage:
watch -n 5 'echo "Open FDs: $(ls /proc/$(pgrep -f your-app | head -1)/fd | wc -l) / $(cat /proc/$(pgrep -f your-app | head -1)/limits | grep "Max open files" | awk "{print \$4}")"'
For anything beyond a single server, wire the same /proc/<PID>/fd count into whatever monitoring stack you already run (Prometheus node_exporter exposes this as process_open_fds) and alert at 80% of the configured limit, not at 100%.
Frequently Asked Questions
What's a reasonable file descriptor limit for a production web server? 65535 is a common, safe starting point for a Node.js or Laravel app behind Nginx under real traffic. Very high-connection-count services (large WebSocket servers, big reverse proxies) sometimes go higher, but 65535 covers the vast majority of production workloads without masking an underlying leak.
Does raising ulimit -n use more memory even if I'm not near the limit?
No — the limit is just a ceiling. A process holding 200 open file descriptors against a limit of 65535 uses the same memory as it would against a limit of 1024. Raising the ceiling costs nothing until you actually approach it.
Why does the error come back after I restart the service, even after changing /etc/security/limits.conf?
Almost always because the service is managed by systemd and never went through the login shell that limits.conf (PAM) applies to. Use systemctl edit and LimitNOFILE instead, as shown above.
Can Docker containers hit this too?
Yes — a containerized process is still bound by the host's limits unless you explicitly raise them. Pass --ulimit nofile=65535:65535 to docker run, or set nofile under default-ulimits in /etc/docker/daemon.json for every container on that host.
Key Takeaways
"Too many open files" is a limits problem on the surface, but treat it as two separate tasks: raise fs.file-max and the per-process nofile limit (via limits.conf for login-shell processes, or LimitNOFILE in a systemd override for services), and separately check whether the descriptor count was actually climbing toward that ceiling on its own — because if it was, the real fix is finding and closing the leaking connections in your application code, not just moving the ceiling further out.
0 Comments
No comments yet — be the first to share your thoughts.