📑 Daftar Isi
- Why File Descriptor Limits Matter More Than You Think
- Checking Your Current File Descriptor Limits
- Step 1: Edit /etc/security/limits.conf
- Step 2: Check /etc/security/limits.d/ for Overrides
- Step 3: Tune the System-Wide Limit via /etc/sysctl.conf
- Step 4: Set Limits for Systemd Services
- Step 5: Verify Everything
- Quick Reference: Recommended Values by Server Type
- Troubleshooting: When Changes Don't Take Effect
- 1. Old SSH Session Still Active
- 2. Systemd Overrides limits.conf
- 3. PAM Module Not Enabled
- 4. Wrong User or Wildcard Scope
- 5. fs.file-max Too Low
- Q: What's the difference between soft and hard limits?
- Q: Why use 65535 instead of 65536?
- Q: Does increasing file descriptors consume more RAM?
- Q: Do I need to reboot the server after changing limits.conf?
- Q: Is 65535 enough or do I need more?
So here’s the deal. If you’ve ever stared at a “Too many open files” error at 2 AM while your production server is choking on connections, you already know why this matters. File descriptors. That one sysadmin parameter that most people set up once — or never — and then wonder why everything falls apart under load.
I’ve been there. More times than I’d like to admit. And every single time, the root cause was the same: default file descriptor limits that were never tuned. It’s one of those things that works perfectly fine until it doesn’t — and when it doesn’t, it takes your entire stack down with it.
This guide walks you through every step of tuning Linux file descriptors, from checking your current limits to applying permanent changes across multiple init systems. No fluff, no theory-only sections — just the commands and configs you need.
Why File Descriptor Limits Matter More Than You Think
Every time an application opens a file, a socket, a pipe, or even a log stream on Linux, the kernel allocates a file descriptor — essentially a handle that the process uses to interact with that resource. Think of it like parking spots: each open file needs one spot, and your server has a maximum number of spots available.
Here’s the problem: most Linux distributions ship with a default limit of 1024 file descriptors per process. For a simple web server handling a handful of connections, that’s fine. But for a modern stack running Nginx + PHP-FPM + MySQL + Redis + background workers? 1024 gets eaten up fast. We’re talking minutes during peak traffic.
And the worst part? The failure isn’t always obvious. You might get a "Too many open files" error in your logs, or your application might silently start refusing new connections. MySQL could become sluggish before crashing entirely. Or your Node.js app might just… die. No dramatic error message, no warning — it just stops working.
Let me show you what the symptoms typically look like in production:
- Nginx or Apache log fills with
"accept() failed (24: Too many open files)" - MySQL/MariaDB slows down dramatically, then crashes or restarts itself
- 502 Bad Gateway or 503 Service Unavailable errors spike
- Server load average jumps but CPU usage looks normal
- Your monitoring dashboard shows connection timeouts across the board
If any of this sounds familiar, keep reading. The fix is straightforward — but you need to know where to apply it and in what order.
Checking Your Current File Descriptor Limits
Before you change anything, you need to know what you’re working with. Here’s how to check every relevant limit on your system:
Check with ulimit
ulimit -n
This returns the soft limit for file descriptors in your current shell session. The default on most distros is 1024. If that’s what you see, your system hasn’t been tuned yet.
# Check the hard limit too
ulimit -Hn
# Example output on a tuned system:
# 65535
Check via /proc
cat /proc/sys/fs/file-nr
This gives you three numbers. The first is the currently allocated file descriptors, the second (always 0 on modern kernels) is the number of free file descriptors, and the third is the system-wide maximum:
# Example output:
# 4128 0 9223372036854775807
# 4128 = currently in use
# 9223372036854775807 = system max (practically unlimited)
Check per-process usage
# Find the PID of your process
PID=$(pidof nginx)
# Count open file descriptors
ls -la /proc/$PID/fd | wc -l
# See details of what's open
ls -la /proc/$PID/fd | head -20
This is incredibly useful for identifying which process is consuming the most file descriptors. In most web stacks, it’s either Nginx or MySQL that leads the pack.
Check configured limits
# Check limits.conf
grep -i nofile /etc/security/limits.conf
# Check override files
grep -ri nofile /etc/security/limits.d/
If either of these returns nothing or shows a value of 1024, you’ve found your problem.
| Command | What It Checks | Expected Output (Tuned) |
|---|---|---|
ulimit -n |
Soft limit per-user | 65535 |
ulimit -Hn |
Hard limit per-user | 65535 |
cat /proc/sys/fs/file-nr |
System-wide usage vs max | 4128 0 9223372036854775807 |
ls /proc/PID/fd | wc -l |
Open FDs for specific process | Varies by workload |
grep nofile /etc/security/limits.conf |
Configured user limits | * soft nofile 65535 |
Step 1: Edit /etc/security/limits.conf
This is the primary file for setting per-user file descriptor limits. Open it with your preferred editor:
sudo nano /etc/security/limits.conf
Add these lines at the bottom of the file:
# File descriptors limit
* soft nofile 65535
* hard nofile 65535
root soft nofile 65535
root hard nofile 65535
Here’s what each field means:
*applies to all users except rootrootgets its own line because some distros handle root differentlysoft= the default limit given to new processeshard= the maximum limit a user can set for themselves viaulimitnofile= number of file descriptors65535= sufficient for virtually all use cases
IMPORTANT: After editing this file, you MUST log out and log back in (or open a new SSH session) for the changes to take effect. Existing sessions will NOT pick up the new limits.
Step 2: Check /etc/security/limits.d/ for Overrides
Some distributions — especially CentOS/RHEL — include override files in /etc/security/limits.d/ that take precedence over limits.conf. If there’s a file there with nofile settings, your limits.conf changes might be ignored.
# List all files in limits.d
ls -la /etc/security/limits.d/
# Check contents
cat /etc/security/limits.d/20-nproc.conf
# Or specifically:
cat /etc/security/limits.d/*-nofile.conf 2>/dev/null
If you find nofile settings there, edit or remove them as needed. Files in limits.d/ are read AFTER limits.conf, so their values override the main config.
Step 3: Tune the System-Wide Limit via /etc/sysctl.conf
Beyond per-user limits, there’s also a kernel-level global limit that controls the total number of file descriptors the entire system can allocate. This is separate from the per-process limit.
sudo nano /etc/sysctl.conf
Add these lines:
# Maximum number of file descriptors system-wide
fs.file-max = 2097152
# For systems with 2GB+ RAM:
# fs.file-max = 4194304
Apply immediately without rebooting:
sudo sysctl -p
Verify the change:
cat /proc/sys/fs/file-max
# Output: 2097152
A value of 2097152 (2 million) works for most servers. If your machine has 4GB+ RAM, bumping it to 4194304 (4 million) is perfectly safe and gives you plenty of headroom.

Step 4: Set Limits for Systemd Services
This is the step that trips up almost everyone. If your application runs as a systemd service (and on modern systems, most do), systemd has its own file descriptor limit that OVERRIDES limits.conf. You need to set it explicitly in the service unit file.
# Example for Nginx
sudo systemctl edit nginx.service
Add these lines in the [Service] section:
[Service]
LimitNOFILE=65535
Then reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart nginx
The same applies to every service in your stack:
| Service | Edit Command | LimitNOFILE Value |
|---|---|---|
| Nginx | systemctl edit nginx |
65535 |
| Apache/httpd | systemctl edit apache2 or httpd |
65535 |
| MySQL/MariaDB | systemctl edit mariadb |
65535 |
| PHP-FPM | systemctl edit php8.2-fpm |
65535 |
| Redis | systemctl edit redis-server |
65535 |
| PostgreSQL | systemctl edit postgresql |
65535 |
| Node.js (PM2) | systemctl edit pm2- |
65535 |
Step 5: Verify Everything
After applying all changes, verification is mandatory. Don’t just assume it worked.
# 1. Check global limit
cat /proc/sys/fs/file-max
# Should show: 2097152
# 2. Open a NEW SSH session (not the old one!)
# Then check:
ulimit -n
# Should show: 65535
ulimit -Hn
# Should show: 65535
# 3. Check for a specific service (e.g., Nginx)
sudo systemctl show nginx -p LimitNOFILE
# Should show: LimitNOFILE=65535
# 4. Check current open FDs for a running process
pidof nginx | xargs -I{} ls /proc/{}/fd | wc -l
# Shows how many FDs Nginx is currently using
If any of these still shows 1024, go back and check which step you missed. The most common reasons: forgot to log out and back in after editing limits.conf, or there’s an override in /etc/security/limits.d/.
Quick Reference: Recommended Values by Server Type
| Server Type | nofile (per-user) | fs.file-max | Notes |
|---|---|---|---|
| Small VPS (1 vCPU, 1GB RAM) | 32768 | 524288 | Sufficient for 1-2 web apps |
| Medium VPS (2 vCPU, 4GB RAM) | 65535 | 2097152 | Web + DB + cache on one box |
| Dedicated (8+ CPU, 32GB+ RAM) | 131072 | 4194304 | High traffic, multiple services |
| Database-heavy server | 131072 | 4194304 | MySQL/PG with many connections + replication |
| Reverse proxy / load balancer | 131072 | 4194304 | Many concurrent upstream connections |
Troubleshooting: When Changes Don’t Take Effect
You’ve followed all the steps but the error persists. Don’t panic — here are the most common gotchas that catch even experienced sysadmins:
1. Old SSH Session Still Active
New limits only apply to NEW login sessions. If you’ve been sitting in the same SSH terminal since before you made changes, that session still has the old limits. Open a new terminal, or log out and log back in.
2. Systemd Overrides limits.conf
This is the big one. If your service runs under systemd (check with systemctl status service-name), systemd’s own LimitNOFILE setting takes priority. You MUST set it in the unit file.
3. PAM Module Not Enabled
Make sure pam_limits.so is active in your PAM configuration:
grep pam_limits /etc/pam.d/sshd
# Should show: session required pam_limits.so
4. Wrong User or Wildcard Scope
Verify that your limits.conf entries apply to the correct user. If Nginx runs as www-data, the * wildcard should cover it. But if you’re using a custom user, add a specific line for that user.
5. fs.file-max Too Low
If fs.file-max is lower than the cumulative nofile requests from all processes, the kernel may silently ignore some settings. Always set fs.file-max to a value higher than your total expected file descriptor usage.
Q: What’s the difference between soft and hard limits?
The soft limit is the default value given to new processes. The hard limit is the ceiling that a user can raise their soft limit up to (via ulimit -n). A user can increase the soft limit up to the hard limit, but cannot increase the hard limit without root access. Think of the hard limit as the ceiling and the soft limit as the current setting.
Q: Why use 65535 instead of 65536?
65535 is the maximum value for an unsigned 16-bit integer. Some applications and kernel internals can behave unexpectedly with exactly 65536 due to overflow issues in certain implementations. Using 65535 is a safe, widely-adopted convention that avoids these edge cases entirely.
Q: Does increasing file descriptors consume more RAM?
Not significantly. Each file descriptor consumes roughly 100-200 bytes of kernel memory. Even 1 million file descriptors would only use about 100-200 MB of kernel memory. So you don’t need to worry about RAM running out just because you raised this limit.
Q: Do I need to reboot the server after changing limits.conf?
No, a full server reboot isn’t required. You just need to log out and log back in for the affected users. For systemd services, restarting the service (not the server) is sufficient. That said, if you want to be absolutely sure everything picks up the changes, a reboot doesn’t hurt.
Q: Is 65535 enough or do I need more?
65535 covers about 95% of use cases. However, if you’re running WebSocket servers with thousands of simultaneous connections, a reverse proxy handling many upstream targets, or a container-heavy environment with Docker/Podman, consider bumping it to 131072 or even 1048576. Just remember to increase fs.file-max accordingly.
Look, there’s no excuse for running a production server with default 1024 file descriptors in 2026. The steps above take maybe 10 minutes total. Do it now, verify it works, and move on with your life. Don’t wait for that 2 AM alert that forces you to scramble. Tune it today, sleep better tonight. Take it seriously — this is one of those config changes where skipping it has zero upside and catastrophic downside.