If your VPS logs are suddenly full of lines like Too many open files or EMFILE, don't panic and don't reboot yet. This is almost never a sign your server is dying — it's Linux telling you that a process has hit its file descriptor ceiling. We see this a lot on VPS accounts running Nginx + PHP-FPM + MySQL under real traffic, especially right after a WordPress site starts getting decent visitors or a Node app opens a lot of concurrent connections. Here's how to diagnose it properly and fix it so it doesn't come back next week.
Symptom: What You'll Actually See
The exact wording depends on which service is choking, but it usually looks like one of these:
nginx: [emerg] accept4() failed (24: Too many open files)in/var/log/nginx/error.log- PHP-FPM error log showing
Too many open filesright before requests start timing out - MySQL/MariaDB throwing
Can't create/write to file '...' (Errcode: 24 - Too many open files) - A generic app crash with
EMFILE: too many open files, open '...'in a Node.js or Python process - SSH itself becomes flaky, or new SSH sessions refuse to open, once the whole system runs dry
Everything else on the box may look fine — CPU and RAM can be sitting comfortably under load — because this isn't a resource shortage in the usual sense. It's a hard cap on how many file handles (which includes sockets, not just literal files) one process, or the whole kernel, is allowed to have open at once.
Cause: Three Different Limits Stack Up
People usually assume there's one "open files" setting. There are actually three layers, and hitting any one of them produces the same error:
| Layer | What it controls | Where it lives |
|---|---|---|
| Per-process soft/hard limit | Max descriptors one process (e.g. one nginx worker) can open | ulimit -n, /etc/security/limits.conf |
| Per-service systemd limit | Overrides the process limit for a specific systemd unit | LimitNOFILE= in the unit or a drop-in override |
| System-wide kernel limit | Total descriptors the whole kernel will hand out to everyone | fs.file-max in /etc/sysctl.conf |
On most modern distros (AlmaLinux, Rocky, Ubuntu, Debian) almost every service that matters — nginx, mysqld, php-fpm — runs as a systemd unit, not a login shell process. That's the detail that trips people up: they carefully raise ulimit -n for their SSH session, confirm it with ulimit -n showing 65535, restart nginx, and the error keeps happening. That's because systemd ignores /etc/security/limits.conf for services it starts directly at boot — it only applies to PAM login sessions. The systemd unit has its own default, usually 1024 or 4096, and that's the one actually in force.
Fix: Raise Limits at Every Layer That Applies
1. Check current limits first
Find the actual limit a running process has, not what you assume it has:
ps -ef | grep nginx
cat /proc/<PID>/limits | grep "open files"
That shows the real soft and hard limit the process is running under — the only numbers that matter.
2. Fix the systemd service limit (the one that actually matters for daemons)
Create a drop-in override rather than editing the vendor unit file directly, so a package update doesn't wipe your change:
sudo systemctl edit nginx
# add these lines in the editor that opens:
[Service]
LimitNOFILE=65535
Do the same for mysqld (or mariadb) and php-fpm:
sudo systemctl edit mariadb
sudo systemctl edit php-fpm
# same [Service] / LimitNOFILE=65535 block in each
Then reload systemd and restart the service:
sudo systemctl daemon-reload
sudo systemctl restart nginx mariadb php-fpm
3. Raise the PAM/login limits too (for cron, WP-CLI, manual scripts)
Edit /etc/security/limits.conf:
* soft nofile 65535
* hard nofile 65535
root soft nofile 65535
root hard nofile 65535
This covers anything launched from an interactive shell or a cron job under PAM, which the systemd override above does not.
4. Raise the kernel ceiling
If the whole system is straining (lots of services, lots of visitors, a busy mail server), bump the global cap in /etc/sysctl.conf:
fs.file-max = 2097152
Apply it without a reboot:
sudo sysctl -p
5. Tune the application layer, not just the OS
Once the ceiling is raised, make sure nginx is actually configured to use it. In nginx.conf:
worker_rlimit_nofile 65535;
events {
worker_connections 8192;
}
For PHP-FPM, check the pool's rlimit_files directive in /etc/php-fpm.d/www.conf (or your pool's config file) and set it to match, e.g. rlimit_files = 65535.
Prevention: Don't Wait for the Next Traffic Spike
- Monitor descriptor usage, not just CPU/RAM.
lsof | wc -landcat /proc/sys/fs/file-nrgive you a quick read on how close you are to the ceiling. - Set alerts before you hit 100%. If you're already running Netdata or a similar monitoring stack, add a check on open file descriptors as a percentage of the limit, not just an absolute number.
- Re-check limits after any OS or package upgrade. A distro upgrade can silently reset systemd unit defaults, undoing a fix you made months ago.
- Watch for file descriptor leaks in application code. If usage climbs steadily over days rather than spiking with traffic, the real bug is probably a script or app that opens files/sockets and never closes them — raising limits just delays the eventual crash.
- Keep MySQL's own limit in sync. MariaDB also has an
open_files_limitsetting inmy.cnfthat should be raised alongside the systemd override, or MySQL will still cap itself below what the OS now allows.
On a SkyServer VPS you have full root access, so all of the changes above are things you can make yourself over SSH. If you're not comfortable editing systemd units or sysctl values directly, open a support ticket with the exact error line from your log file and we'll apply the right limits for your stack.
Frequently Asked Questions
Will raising ulimit values slow down my server or use more RAM?
Raising the limit itself costs nothing — it's just a ceiling. Each actually-open file descriptor uses a small, fixed amount of kernel memory, so RAM usage only grows if your services genuinely open more files or connections, which is exactly what you want them to be able to do under load.
Why did ulimit -n show a high number in my SSH session, but nginx still hit the limit?
Because systemd starts nginx independently of your shell session and applies its own default LimitNOFILE, ignoring /etc/security/limits.conf entirely for services launched at boot. You have to set the limit on the systemd unit itself with systemctl edit.
What's a sane value to set LimitNOFILE to?
65535 is a safe, commonly used ceiling for a small-to-medium VPS running nginx, PHP-FPM, and MySQL together. Large, high-traffic setups sometimes go higher, but jumping straight to a huge number rarely helps and can mask a real leak elsewhere.
Do I need to reboot the whole VPS after making these changes?
No. sysctl -p applies kernel-level changes immediately, and systemctl daemon-reload plus a service restart applies the systemd overrides. A full reboot is only needed if you want to double-check the settings survive a cold start.
How do I tell the difference between normal traffic growth and a file descriptor leak?
Check descriptor counts at a quiet hour versus a busy hour. If the number drops back down when traffic drops, it's normal load. If it keeps climbing even overnight with no traffic, or never goes back down after a spike, that's a leak in an application or script that isn't closing what it opens.
