← Back to DevBytes

Fix 'Too many open files' in Linux: Complete Troubleshooting Guide

Understanding the "Too many open files" Error

In Linux, everything is a file. This foundational design principle means that regular files, directories, network sockets, pipes, and even hardware devices are all represented as file descriptors. When a process opens one of these resources, the kernel allocates a file descriptor — an integer handle that the process uses to read from or write to the resource. The error message Too many open files (often appearing in logs or terminal output as EMFILE or errno 24) occurs when a process hits its limit for the number of file descriptors it can hold open simultaneously.

This error can manifest in various ways: a web server refusing new connections, a database failing to open data files, a build tool crashing mid-operation, or a desktop application suddenly becoming unresponsive. Understanding this limit, how to inspect it, and how to adjust it is an essential troubleshooting skill for any developer or system administrator working on Linux systems.

Why File Descriptor Limits Exist

The limits are not arbitrary — they serve two critical purposes. First, they prevent a single rogue process from exhausting kernel memory by opening millions of files. Each open file descriptor consumes kernel memory for its associated data structures (the file struct, dentry cache entries, and inode cache entries). Second, they act as a safety net against file descriptor leaks, where a program opens files but forgets to close them. Without limits, such a leak could gradually consume all available system resources until the machine becomes unstable.

Linux implements file descriptor limits at two distinct levels:

Diagnosing the Current Limits

Before fixing anything, you need to understand exactly which limit is being hit. Start by checking both per-process and system-wide values.

Checking Per-Process Soft and Hard Limits

Each process has a soft limit (the effective ceiling it can currently use) and a hard limit (the maximum value the process can raise its soft limit to). A regular user can increase its soft limit up to the hard limit without special privileges. Only root can raise the hard limit.

To see the limits for your current shell session:

# Check all current limits
ulimit -a

# Check only the open files limit (soft)
ulimit -n

# Check both soft and hard open files limits
ulimit -Sn
ulimit -Hn

For an already running process, inspect its limits via the proc filesystem. Replace <PID> with the actual process ID:

# Show limits for a specific process
cat /proc/<PID>/limits

# Specifically for open files (Max open files)
grep 'Max open files' /proc/<PID>/limits

This output shows both the soft limit (Max open files line, Soft Limit column) and the hard limit (Hard Limit column), along with the units. If the soft limit says 1024 and the process needs to handle thousands of connections, you have found your culprit.

Checking System-Wide Limits

The kernel tracks global file descriptor usage. Check the system-wide maximum and current usage like this:

# Total system-wide max open files
cat /proc/sys/fs/file-max

# Current number of allocated file descriptors system-wide
cat /proc/sys/fs/file-nr

The file-nr output shows three numbers separated by tabs: currently allocated file handles, currently unused file handles (but still allocated), and the maximum number of file handles (same as file-max). If the first number is close to the third, the system as a whole is approaching exhaustion — a rare but critical situation.

You can also see which file descriptors a specific process currently holds open:

# List all open file descriptors for process <PID>
ls -l /proc/<PID>/fd/

# Count how many file descriptors are currently open for that process
ls /proc/<PID>/fd/ | wc -l

# Use lsof for a detailed view
lsof -p <PID>

The lsof output reveals exactly which files, sockets, and pipes are open, helping you identify leaks — for instance, hundreds of open sockets in CLOSE_WAIT state or thousands of open regular file handles that should have been closed.

Fixing the Error Temporarily

Sometimes you need an immediate fix to keep a service running while you work on a permanent solution. These changes apply only to the current shell session and its child processes.

Raising the Soft Limit in the Current Shell

# Raise soft limit to 4096 for this session
ulimit -n 4096

# Verify the change
ulimit -n

You can set the soft limit to any value up to the hard limit. If you need to go higher than the current hard limit, you must first raise the hard limit (which requires root privileges):

# As root, raise both hard and soft limits for this session
ulimit -Hn 65536
ulimit -Sn 65536

# Or in one command: set both soft and hard to the same value
ulimit -n 65536

After setting the limit in the shell, launch the affected program from that same shell so it inherits the new limit. This approach works well for testing but does not survive a logout or system reboot.

Adjusting a Running Process (Using prlimit)

The prlimit command (available in the util-linux package on most distributions) allows you to change resource limits for an already running process, provided you have appropriate permissions:

# Raise open files soft limit for an existing process to 8192
prlimit --pid <PID> --nofile=8192:65536

# Check the new limits for that process
prlimit --pid <PID> --nofile

The syntax 8192:65536 sets the soft limit to 8192 and the hard limit to 65536. This is extremely useful for fixing a production issue without restarting a critical service. Note that only processes running as the same user (or root) can be modified.

Making Changes Permanent

Permanent configuration requires editing system files so the higher limits apply across reboots and to all relevant processes.

Per-User Limits via /etc/security/limits.conf

The /etc/security/limits.conf file (and files in /etc/security/limits.d/) controls resource limits applied through the PAM (Pluggable Authentication Modules) system. This affects login sessions and services that use PAM for authentication.

# Example entries in /etc/security/limits.conf
# <domain>  <type>  <item>  <value>

# Set open files limit for user 'deploy' to 65536
deploy   soft   nofile   65536
deploy   hard   nofile   65536

# Set limits for all users in the 'developers' group
@developers   soft   nofile   32768
@developers   hard   nofile   65536

# Set a generous default for all users
*   soft   nofile   8192
*   hard   nofile   16384

# Set specifically for root
root   soft   nofile   65536
root   hard   nofile   1048576

The domain can be a username, a group name prefixed with @, or * for the default. The type is either soft, hard, or - (which sets both simultaneously). After editing this file, the new limits take effect when the user logs in again (starts a new session).

To verify that PAM is actually applying limits, ensure the pam_limits.so module is included in the relevant PAM configuration files (usually /etc/pam.d/common-session or /etc/pam.d/login):

# Check that this line exists in your PAM config
session    required   pam_limits.so

Systemd Service Unit Overrides

Modern Linux distributions use systemd as the init system. Systemd services do not inherit limits from /etc/security/limits.conf by default because systemd does not process user login PAM sessions for services. Instead, you must configure limits directly in the service unit file or via a drop-in override.

To increase the open files limit for a specific systemd service (for example, nginx or mysql):

# Create an override directory for the service
sudo mkdir -p /etc/systemd/system/nginx.service.d/

# Create an override file with the new limits
sudo tee /etc/systemd/system/nginx.service.d/override.conf << 'EOF'
[Service]
LimitNOFILE=65536
LimitNOFILESoft=65536
EOF

# Reload systemd and restart the service
sudo systemctl daemon-reload
sudo systemctl restart nginx

# Verify the limit took effect
systemctl show nginx | grep LimitNOFILE

You can also edit the main service file directly (usually located at /lib/systemd/system/<service>.service), but using an override file prevents your changes from being overwritten by package updates. The key directives are:

For user services (those run under a user's systemd instance with systemctl --user), you can configure limits globally in /etc/systemd/user.conf by setting DefaultLimitNOFILE=65536.

Adjusting the System-Wide Maximum

If the global file descriptor pool is approaching exhaustion (rare, but possible on heavily loaded servers with many processes), increase the kernel's system-wide limit:

# Check current system-wide max
cat /proc/sys/fs/file-max

# Temporarily set a new system-wide maximum
sudo sysctl -w fs.file-max=2097152

# Make it permanent by adding to /etc/sysctl.conf or a file in /etc/sysctl.d/
echo "fs.file-max=2097152" | sudo tee /etc/sysctl.d/99-file-max.conf

# Apply the permanent setting
sudo sysctl --system

A common formula for calculating a safe file-max value is to multiply the maximum number of concurrent processes you expect by the per-process file limit you have configured, then add a buffer. For instance, if you expect 500 processes each with a limit of 65536, a system-wide limit of 2097152 gives comfortable headroom. The kernel also requires approximately 1 MB of memory per 1000 allocated file descriptors, so ensure your system has adequate RAM for the value you choose.

Identifying and Fixing File Descriptor Leaks

Sometimes raising limits only masks the real problem: a file descriptor leak in application code. A well-written program closes every file descriptor it opens as soon as it is no longer needed. Leaks happen when error paths forget to close resources, when loops accumulate open handles, or when libraries mismanage their internal descriptors.

Spotting a Leak in Real Time

Monitor a suspicious process over time to see if its file descriptor count grows monotonically:

# Watch the FD count of process 12345 every 2 seconds
watch -n 2 'ls /proc/12345/fd/ | wc -l'

# Use a quick shell loop to log counts over time
while true; do
  count=$(ls /proc/12345/fd/ 2>/dev/null | wc -l)
  echo "$(date --iso-8601=seconds): FD count = $count"
  sleep 5
done

If the count increases steadily without ever decreasing, you likely have a leak. Use lsof to categorize the open descriptors:

# Show open file descriptors grouped by type
lsof -p 12345 | awk '{print $5}' | sort | uniq -c | sort -rn

# Focus on network connections (sockets)
lsof -p 12345 -i

# Look for deleted files still held open (common leak pattern)
lsof -p 12345 | grep '(deleted)'

Files marked (deleted) in lsof output indicate that the file has been unlinked from the filesystem but the process still holds the descriptor open. This prevents the disk space from being freed and contributes to the FD count. This commonly happens with log files that are rotated by deleting the original file while the application still writes to the old descriptor.

Common Leak Patterns to Look For

Application-Specific Configuration

Many server applications have their own configuration directives for maximum open files, which override or supplement the OS-level limits. Setting these correctly ensures the application manages its resources efficiently.

Nginx

# In nginx.conf
worker_rlimit_nofile 65536;

events {
    worker_connections 4096;
    # total connections per worker = worker_connections * 2 (for proxying)
}

The worker_rlimit_nofile directive sets the per-process file descriptor limit for each Nginx worker process. The worker_connections controls how many simultaneous connections each worker can handle. Each connection consumes at least one file descriptor (two if proxying), so ensure worker_rlimit_nofile is at least double worker_connections plus overhead for static files, logs, and other resources.

Apache HTTPD

# In envvars or the systemd unit file
# Set the ulimit for Apache processes
ulimit -n 65536

# In the prefork MPM configuration
MaxRequestWorkers 256
# Each worker may need multiple FDs, so plan accordingly

MySQL / MariaDB

# In my.cnf or my.ini
open_files_limit = 65536
max_connections = 500
table_open_cache = 2000

MySQL's open_files_limit sets the maximum number of file descriptors the mysqld process will attempt to use. It must accommodate table files, socket connections, temporary files, and internal needs. A safe formula: open_files_limit >= max_connections * 2 + table_open_cache * 2 + extra overhead.

PostgreSQL

# In postgresql.conf
max_files_per_process = 4096
max_connections = 200

Each PostgreSQL backend process opens its own set of files. The max_files_per_process setting limits file descriptors per backend, helping prevent one connection from consuming the entire pool.

Monitoring and Alerting

Proactive monitoring prevents surprises. Set up checks that alert you before limits are actually hit.

Script for Nagios / Sensu / Custom Monitoring

#!/bin/bash
# Check file descriptor usage for a critical process
PROCESS_NAME="nginx"
PID=$(pgrep -f "$PROCESS_NAME" | head -1)
SOFT_LIMIT=$(grep 'Max open files' /proc/$PID/limits | awk '{print $4}')
CURRENT_FD=$(ls /proc/$PID/fd/ 2>/dev/null | wc -l)
USAGE_PCT=$(( CURRENT_FD * 100 / SOFT_LIMIT ))

if [ $USAGE_PCT -gt 80 ]; then
  echo "WARNING: $PROCESS_NAME FD usage at ${USAGE_PCT}% ($CURRENT_FD/$SOFT_LIMIT)"
  exit 1
fi
echo "OK: $PROCESS_NAME FD usage at ${USAGE_PCT}% ($CURRENT_FD/$SOFT_LIMIT)"
exit 0

Using Prometheus Node Exporter

The Prometheus node exporter exposes file descriptor metrics that you can query and visualize. Key metrics include node_filefd_allocated, node_filefd_maximum, and per-process FD counts. Set up alerting rules in Prometheus Alertmanager to notify you when FD usage exceeds, say, 80% of the configured limit.

# Example Prometheus alert rule (promql)
# Alert if file descriptor usage ratio exceeds 0.8 for any process
(node_filefd_allocated / node_filefd_maximum) > 0.8

Best Practices Summary

Conclusion

The "Too many open files" error is a classic Linux troubleshooting scenario that reveals the layered nature of resource limits in the operating system. By understanding the distinction between per-process soft and hard limits, system-wide kernel limits, and how each layer — PAM, systemd, and the application itself — contributes to the effective ceiling, you can systematically diagnose and resolve the issue. The immediate fix is straightforward: raise the relevant limit. But the professional approach combines limit adjustment with leak detection, proper monitoring, and configuration management to ensure the problem stays solved. With the techniques and commands covered in this guide, you should be able to confidently handle this error in any Linux environment, from a single developer workstation to a fleet of production servers.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles