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:
- Per-process limits — enforced via
ulimitand therlimitstructure, controlling how many file descriptors a single process (and its child processes) may open. - System-wide limits — governed by the
/proc/sys/fs/file-maxkernel parameter, which caps the total number of open file descriptors across all processes on the entire system.
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:
LimitNOFILE=— sets both soft and hard limits to the same value (shorthand)LimitNOFILESoft=— sets only the soft limitLimitNOFILEHard=— sets only the hard limit
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
- Unclosed sockets in CLOSE_WAIT state — The remote peer closed the connection, but the local application never called
close(). These accumulate and eat file descriptors. - Forgotten
opendir()withoutclosedir()— Directory handles consume file descriptors just like regular files. - Event loop libraries — Some event-driven frameworks register file descriptors for monitoring but fail to deregister and close them when connections terminate.
- Forking without closing inherited descriptors — When a process forks, the child inherits all open descriptors. If the child doesn't need them, they should be closed early in the child's execution.
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
- Set limits based on actual workload measurements — Don't blindly set limits to 999999. Profile your application under realistic load, count the file descriptors it actually uses, and set limits with a comfortable buffer (2x–4x the peak observed usage).
- Configure limits at every layer — OS level (
limits.conf), init system level (systemdunit overrides), and application level (daemon-specific config directives). Missing any layer can leave your higher limits ineffective. - Separate soft and hard limits thoughtfully — Set the soft limit to what the application normally needs and the hard limit as a higher safety ceiling. This allows the application to raise its own limit if needed while still preventing runaway resource consumption.
- Fix leaks, don't just raise limits — A file descriptor leak will eventually exhaust any limit you set. Use monitoring to detect growth patterns, then fix the root cause in the code.
- Document your limit configuration — In a team environment, ensure that
limits.confentries, systemd overrides, and application config changes are committed to your configuration management system (Ansible, Puppet, Chef) or infrastructure-as-code repository. - Test limits in staging first — A limit that works on a development laptop may be inadequate under production concurrency. Replicate production-like load in staging and verify that FD usage stays well below the configured limits.
- Remember child processes inherit limits — When a shell spawns a program, the child inherits the shell's
ulimit. When systemd starts a service, it applies the unit'sLimitNOFILE. When a process forks, the child gets a copy of the parent's limits. Trace the full chain to ensure every link is properly configured.
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.