Setting Linux Resource Limits with ulimit, limits.conf, and systemd
Too many open files and runaway processes come down to resource limits. Here's how ulimit, limits.conf, and systemd directives really interact.
- #linux
- #ulimit
- #rlimits
- #systemd
- #tuning
- #troubleshooting
“Too many open files.” If you run servers long enough, you’ll meet that error, and you’ll discover that fixing it is weirdly confusing — you bump ulimit, the limit doesn’t change, you bump a different file, still nothing, and meanwhile the service keeps falling over at exactly 1024 file descriptors. The confusion is real because there are three different systems setting limits, and they apply in different contexts. Here’s how they actually interact and how to set limits that stick.
What rlimits even are
The kernel enforces per-process resource limits (“rlimits”): max open files, max processes, max locked memory, core dump size, stack size, and more. Each has a soft limit (the current value, which a process can raise up to the hard cap) and a hard limit (the ceiling, raised only by root).
See the limits of your current shell:
ulimit -a # all soft limits
ulimit -Hn # hard limit for open files
ulimit -Sn # soft limit for open files
And — critically — the real limits of a running process, which is the only output you should trust:
cat /proc/$(pgrep -f myservice)/limits
When someone says “I set the limit but it’s not working,” this file ends the argument. It shows what the process actually has.
The three systems that set limits
1. ulimit (shell, interactive) — sets limits for the current shell and its children. Great for testing, useless for daemons because they don’t start from your shell.
ulimit -n 65535 # raise open-files soft limit for this shell session
A non-root user can only raise the soft limit up to the hard limit and can only lower the hard limit. So ulimit -n 65535 fails if the hard cap is 1024 — you have to raise the hard cap first (as root) via the next system.
2. /etc/security/limits.conf (PAM, login sessions) — applies to processes started through a PAM login (SSH, console, su -, login). This is the right place for limits on users’ interactive sessions and for daemons started by traditional init scripts.
# /etc/security/limits.conf — domain type item value
* soft nofile 65535
* hard nofile 65535
deploy soft nproc 4096
deploy hard nproc 8192
The crucial caveat: this only works if pam_limits.so is in the PAM stack (it usually is for login and sshd). It does not apply to systemd services, which is the trap that catches most people.
3. systemd service directives (the modern way) — this is the one that matters on any current distro, because systemd starts your services and systemd does not read limits.conf. You must set limits in the unit file:
[Service]
LimitNOFILE=65535
LimitNPROC=8192
LimitCORE=infinity
After editing, reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart myservice
cat /proc/$(pgrep -f myservice)/limits # verify it took
This is the single most common fix for “I raised limits.conf but my service still hits 1024.” The service is managed by systemd, which ignored limits.conf entirely.
Mapping the names
ulimit flags, limits.conf items, and systemd directives are the same underlying rlimits with three different naming schemes. The ones you’ll actually use:
| Resource | ulimit | limits.conf | systemd |
|---|---|---|---|
| Open files | -n | nofile | LimitNOFILE |
| Processes/threads | -u | nproc | LimitNPROC |
| Locked memory | -l | memlock | LimitMEMLOCK |
| Core dump size | -c | core | LimitCORE |
| Stack size | -s | stack | LimitSTACK |
The system-wide ceiling
Per-process limits sit under a global cap. Even if you grant a process a huge nofile, the whole system can’t exceed fs.file-max:
cat /proc/sys/fs/file-max # system-wide max open files
sysctl fs.file-max
And current usage:
cat /proc/sys/fs/file-nr # allocated / unused / max
On a box running thousands of connections (a proxy, a database), raise fs.file-max in /etc/sysctl.d/ alongside the per-service limit, or you’ll hit the global wall.
nproc has a similar system-wide cousin in kernel.pid_max, and on busy boxes you may also hit kernel.threads-max.
A decision guide
- Daemon managed by systemd? →
LimitNOFILE=etc. in the unit. (Almost always the answer today.) - Interactive SSH/login sessions or PAM-started processes? →
/etc/security/limits.conf. - Just testing in your current shell? →
ulimit. - Hitting the limit despite raising per-process? → check
fs.file-max/pid_max. - Not sure it applied? →
cat /proc/<pid>/limits, every time.
Verifying under real load
Setting a limit isn’t enough; confirm you’re not approaching it in production. Count a process’s open descriptors:
ls /proc/$(pgrep -f myservice)/fd | wc -l
Compare that to the Max open files line in /proc/<pid>/limits. Alert when you cross, say, 80% of the limit, so you find out before “too many open files” finds you.
Where AI helps
The maddening part of rlimits is the three-systems-that-look-similar confusion. Describe your setup to a model — “service started by systemd, I edited limits.conf, still capped at 1024” — and it’ll immediately point you at LimitNOFILE in the unit, because that mismatch is a known pattern. I keep a few Linux admin prompts for exactly this triage.
Resource limits aren’t hard once you know which of the three systems owns your process. On modern Linux that’s almost always systemd, so reach for LimitNOFILE first — and always confirm with /proc/<pid>/limits rather than trusting that the edit worked.
Generated commands and configs are assistive, not authoritative. Always verify against your own systems before applying changes in production.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.