Fix "Too Many Open Files" on Linux (ulimit)
When a process hits its file descriptor ceiling everything breaks at once; here is how to find the real limit and raise it correctly.
Key takeaways
When a process hits its file descriptor ceiling everything breaks at once; here is how to find the real limit and raise it correctly.
On this page
Fix "Too Many Open Files" on Linux (ulimit)#
Your app was fine at lunch. By 3pm the logs are full of accept: too many open files, new connections hang, and the health check is flapping. The kernel is returning EMFILE because a process tried to allocate a file descriptor beyond its per-process limit. Every socket, pipe, epoll instance, inotify watch, and open file counts against that number.
The error is almost always per-process, not system-wide. That distinction decides where you fix it.
What the error actually means#
A file descriptor (fd) is a small integer the kernel hands back for anything openable. The soft limit RLIMIT_NOFILE caps how many a single process may hold at once. Cross it and open(), socket(), or accept() fail with EMFILE. You will see it as Too many open files in strace, application logs, or a Java java.net.SocketException.
Two limits exist per process. The soft limit is the enforced value. The hard limit is the ceiling a process may raise its own soft limit up to without root. Root can raise the hard limit; unprivileged processes cannot exceed it.
See the current limits#
$ ulimit -Sn # soft
1024
$ ulimit -Hn # hard
524288
ulimit reports the limits of your current shell. It does not tell you what a running daemon inherited. The daemon may have been started by systemd, cron, or an init script with a completely different environment. Read its actual limits from procfs:
$ cat /proc/$(pgrep -o nginx)/limits | grep -i "open files"
Max open files 1024 4096 files
That first column is the soft limit the process is really running with. If it says 1024 while your shell says 524288, your shell config is irrelevant to that process.
Count how many fds a process is holding#
$ ls /proc/12345/fd | wc -l
1017
At 1017 against a soft limit of 1024 you are about to fall over. To see what those fds are, lsof is the readable view:
$ lsof -p 12345 | awk '{print $5}' | sort | uniq -c | sort -rn
880 IPv4
71 REG
12 unix
8 DIR
5 CHR
880 open IPv4 sockets on a process that should hold a few dozen is a leak signature, not a limit that is too low. Hold that thought.
Raise the limit the right way, per context#
There is no single place to change this. Where you edit depends on how the process starts.
Interactive shell or a script you launch by hand: ulimit -n 65536 in that shell, before starting the program. It applies to that shell and its children only, and it cannot exceed the hard limit.
Login sessions (SSH, su, getty): edit /etc/security/limits.conf and rely on pam_limits. This path only affects processes started through PAM.
# /etc/security/limits.conf
appuser soft nofile 65536
appuser hard nofile 131072
Confirm pam_limits.so is present in the relevant PAM stack (/etc/pam.d/common-session or /etc/pam.d/sshd), then log out and back in. A common trap: editing limits.conf and expecting a systemd-managed service to pick it up. It will not. PAM is not in that path.
systemd services: set it in the unit, not in limits.conf. systemd ignores pam_limits for services it starts.
# /etc/systemd/system/myapp.service (or a drop-in)
[Service]
LimitNOFILE=65536
Use a drop-in to avoid editing packaged units:
$ sudo systemctl edit myapp
# add the [Service] block above, then:
$ sudo systemctl daemon-reload
$ sudo systemctl restart myapp
daemon-reload alone does not change a running process. The service must restart to inherit the new limit.
The system-wide ceiling#
Per-process limits sit under two kernel-wide sysctls. fs.file-max is the total number of open file handles the whole system will allow. fs.nr_open is the maximum any single process limit may be set to, so a LimitNOFILE higher than fs.nr_open is silently clamped.
$ sysctl fs.file-max fs.nr_open
fs.file-max = 9223372036854775807
fs.nr_open = 1048576
On modern kernels fs.file-max is effectively unbounded, so you rarely touch it. If you genuinely need per-process limits above ~1M, raise fs.nr_open first in /etc/sysctl.d/, then set the higher LimitNOFILE.
Verify it actually applied#
Do not trust the config file. Trust procfs on the live PID after restart:
$ systemctl show myapp -p MainPID --value
12345
$ grep "open files" /proc/12345/limits
Max open files 65536 65536 files
If that still reads 1024, you edited the wrong place for how the process starts. That mismatch is the single most common reason this error "won't go away."
Root cause vs bandaid#
Raising the limit is the correct fix when the workload legitimately needs more fds: a busy reverse proxy, a database with many connections, a service with a large connection pool. It is a bandaid when the fd count climbs without bound. A leak looks like a number that only goes up and never plateaus. Common sources:
- Connection pools: a pool with no max, or code that opens a client per request and never closes it. The fix is in the pool config, not the kernel.
- Unclosed files or sockets: a missing
close()in an error path. Watch/proc/<pid>/fdover time; steady growth under steady load means a leak. - inotify watches: watchers count against
fs.inotify.max_user_watches, a separate limit. A tool watching a huge tree throwsENOSPC("no space left on device") rather than EMFILE, but it is the same class of exhaustion. Raise it withsysctl fs.inotify.max_user_watches=524288if the watch count is real.
If you raise ulimit and the process refills the new headroom in an hour, you bought an hour. Sample the fd count over time before deciding.
This is one entry in the broader Linux troubleshooting guide; the same procfs-first instinct applies when you hit address already in use.
The fix in order#
- Read
/proc/<pid>/limitson the actual running process. Ignore your shell'sulimit. - Count fds with
ls /proc/<pid>/fd | wc -land break them down withlsof -p. Decide leak vs legitimate load. - If it is real load, set the limit where the process starts:
LimitNOFILE=for systemd, limits.conf for PAM logins,ulimit -nfor hand-run scripts. - Restart the process and re-check
/proc/<pid>/limits. Config that is not reflected on the live PID did nothing. - If it is a leak, fix the pool or the missing close. The kernel limit is a fuse, not a solution.
The call we would make on a paging alert: read the live limits, count the fds, and only reach for the config file once lsof proves the process needs the headroom rather than losing it.
Get the DevOps Troubleshooting Cheat Sheet
Subscribe and get our free one-page reference for the errors that eat an afternoon — CrashLoopBackOff, OOMKilled, Terraform state locks, and more — plus new guides as we publish them.
Best Infrastructure-as-Code Tools in 2026 — Terraform, OpenTofu, Pulumi, and More
The IaC landscape fractured after the Terraform license change. This is the map to what each tool is actually best at, and how to choose without regret.
CircleCI Best Practices in 2026: Fast, Safe Pipelines
A production-focused CircleCI guide: orbs, reusable commands and executors, workflows with fan-in/fan-out, contexts and OIDC for secrets, layered caching, test splitting, approval jobs, and dynamic config — with copy-paste examples.
More from Linux
Explore more articles in this category
ext4 vs XFS vs Btrfs: Choosing a Filesystem for a Server
The default filesystem your distro picks is not always the right one for your workload. Here is what actually differs and when each one wins.
journald Log Management: Retention, Filtering, and Forwarding
journald is the default log sink on every systemd distro, and most of it runs on defaults nobody chose. Here is how to actually control it.
DNS Troubleshooting on Linux: A Systematic Approach
\"It's always DNS\" is a joke because the failure modes are so scattered: resolver config, caching, search domains, split DNS. Here is where to actually look.
You might have missed
Evergreen posts worth revisiting.