Linux / Bash

How to Fix "Too Many Open Files" Limit Error on Linux

4 min read by DebuggedIt

Quick answer

Your application starts throwing errors under load, and the underlying cause is that it's hit the maximum number of file descriptors Linux allows it to have...

Your application starts throwing errors under load, and the underlying cause is that it's hit the maximum number of file descriptors Linux allows it to have open at once. This is a resource limit, not a bug in the traditional sense β€” though it very often points at a real leak somewhere in your application's file or socket handling.

The Problem

The application logs an OS-level error instead of the operation it was trying to perform:

Error: EMFILE: too many open files, open '/var/log/app/request-8821.log'

Or, for a Go or Java service specifically:

dial tcp: socket: too many open files
java.io.IOException: Too many open files
	at java.base/sun.nio.ch.FileDispatcherImpl.read0(Native Method)

Checking the current limit for the running process confirms it's hitting a hard ceiling:

$ ulimit -n
1024
File descriptor limit exhaustion soft limit: 1024 fds available to this process 1010 open (files, sockets, pipes) 14 left -> EMFILE soon Fix: raise the limit AND close what you open

Why It Happens

Every open file, socket, pipe, and several other kernel resources on Linux consume one of a process's limited pool of file descriptors, capped by both a soft and a hard limit (ulimit -Sn and ulimit -Hn). The error fires the instant a process tries to open one more than its current limit allows. It shows up for two very different reasons that require different fixes:

  • The limit is genuinely too low for legitimate load β€” a busy web server or database handling thousands of concurrent connections can reasonably need far more than the common Linux default of 1024.
  • A file descriptor leak β€” code that opens files, sockets, or database connections and never closes them, so the count climbs steadily over time regardless of what the limit is set to, until it eventually exhausts whatever ceiling you have.

Distinguishing between these two is the key first step, since raising the limit alone doesn't fix a leak β€” it just delays the crash.

The Fix

First, check the current limits for your shell and for the actual running process (they can differ):

ulimit -Sn
ulimit -Hn
cat /proc/<PID>/limits | grep "Max open files"

To raise the limit for your current shell session temporarily (useful for quick testing):

ulimit -n 65536

For a persistent, system-wide change, edit /etc/security/limits.conf:

sudo nano /etc/security/limits.conf
*    soft    nofile    65536
*    hard    nofile    65536

If the process is managed by systemd, the limits.conf change alone often isn't enough β€” systemd services need their own explicit override, since systemd doesn't always inherit PAM-based limits:

sudo systemctl edit myapp
[Service]
LimitNOFILE=65536

Reload and restart the service so the new limit takes effect:

sudo systemctl daemon-reload
sudo systemctl restart myapp

Now confirm the running process actually picked up the new limit:

cat /proc/$(pgrep myapp)/limits | grep "Max open files"

Still Not Working?

If raising the limit only buys you a little more time before the error returns, you're almost certainly dealing with a leak rather than a legitimately high load. List what a specific process actually has open right now to spot the pattern:

lsof -p $(pgrep myapp) | wc -l
lsof -p $(pgrep myapp) | awk '{print $5}' | sort | uniq -c | sort -rn

A large, steadily growing number of entries of type REG (regular files) or IPv4/IPv6 sockets that never seem to close is the signature of a leak β€” usually a missing close() call in an error path, a database connection pool without proper cleanup, or an HTTP client that doesn't close response bodies. Review any code path that opens a file or connection and confirm it's paired with a defer (in Go), a try-with-resources block (in Java), or an equivalent guaranteed-cleanup pattern for your language, since manual close calls are the ones most likely to get skipped on an early return or exception path.