• Indonesian
  • English
  • Linux File Descriptors Limit Tuning: Complete Step-by-Step

    Kecepatan:
    ⏱ 10 min read

    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.

    Difficulty: Intermediate
    Last Updated: August 2026
    Tested On: Ubuntu 22.04/24.04 LTS, Debian 12, CentOS 7/8/9, Rocky Linux 9, AlmaLinux 9

    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 root
    • root gets its own line because some distros handle root differently
    • soft = the default limit given to new processes
    • hard = the maximum limit a user can set for themselves via ulimit
    • nofile = number of file descriptors
    • 65535 = sufficient for virtually all use cases
    Tip: Why 65535? It’s the maximum value for an unsigned 16-bit integer. It’s a safe, widely-adopted value. If you need more (rare), you can go up to 1048576, but that requires more careful consideration of system resources.

    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.

    linux file descriptors limit tuning via sysctl.conf

    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
    Warning: If you only edit limits.conf but don’t set LimitNOFILE in the systemd unit file, services managed by systemd will STILL use the default limit (usually 1024). This is the #1 reason why people say “I tuned file descriptors but it didn’t work.”

    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.

    Author: Syslog Solutions — NOC & Server Management Team. We handle 500+ servers daily, from shared hosting to enterprise dedicated infrastructure.

    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.