A Too many open files (EMFILE) error often persists despite raising ulimit -n in /etc/security/limits.conf, a file that looks like the obvious place for this setting but simply doesn’t apply to a service managed by systemd.
What ulimit -n actually limits
Every process has a maximum number of simultaneously open file descriptors (files, network sockets, pipes), a limit set by the kernel and configurable via two distinct ceilings: a soft limit, changeable by the process itself up to that ceiling, and a hard limit, the absolute cap no unprivileged process can exceed.
# Current soft limit and maximum hard limit
# for the current shell session
ulimit -Sn
ulimit -Hn
/etc/security/limits.conf: PAM sessions only
limits.conf configures limits via PAM (Pluggable Authentication Modules), a mechanism applying to classic login sessions (SSH, local console, su, sudo), but never to services started directly by systemd, which go through no PAM session at all.
# /etc/security/limits.conf: has strictly no effect
# on a service started by systemd
myuser soft nofile 65536
myuser hard nofile 65536
A systemd service launched at system boot, never going through a user login, entirely ignores this file: raising this limit might fix an error for an interactive shell session, without ever affecting the service, which keeps running under the system’s default limit (often 1024).
LimitNOFILE=: the only setting that matters for systemd
A systemd service inherits its own limit, configured directly in its unit via LimitNOFILE=, independent of anything defined in limits.conf.
# In the .service file, the only line that actually
# affects a systemd-managed service
[Service]
LimitNOFILE=65536
# Checking the effective limit of a running service,
# the only reliable check
cat /proc/$(systemctl show -p MainPID --value my-app)/limits | grep "open files"
Without this explicit setting in the unit, a service inherits a default value (often 1024, sometimes higher depending on the distribution and its system configuration), a limit quickly reached by an application opening many simultaneous network connections or handling a large number of files in parallel.
The diagnostic trap: two checks, not one
Checking ulimit -n from an interactive shell says nothing about the real limit applied to a running systemd service: the two contexts are entirely separate, each with its own configuration source. Confusing the two leads to wrongly concluding that a limits.conf increase fixed the problem, when the service keeps running under its default, unchanged systemd limit.
Takeaway
/etc/security/limits.conf configures limits via PAM, a mechanism applying only to classic login sessions, never to a service started directly by systemd. LimitNOFILE= in the .service file is the only setting that matters for such a service, entirely independent of limits.conf. Checking the effective limit directly via /proc/<pid>/limits rather than trusting ulimit -n in an interactive session avoids wrongly concluding a setting fixed a problem that actually persists, one of the classic traps that stays relevant the moment an application runs as a system service rather than in an interactive session.