LinuxXXXVII · Resource Managementulimits
ulimits and limits.conf - per-user resource limits
What you'll learn
- Read current ulimits
- Set per-user limits via /etc/security/limits.conf
- Distinguish soft and hard limits
- Recognise when limits cause failures
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
ulimits are per-process resource limits: file descriptors, processes, CPU time, memory. This lesson covers how to set and apply them.
Read ulimits
ulimit -a
Output:
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
file size (blocks, -f) unlimited
max locked memory (kbytes, -l) unlimited
max resident set size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 1
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) unlimited
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
Set ulimits in shell
ulimit -n 65536 # open files
ulimit -u 4096 # max processes
These apply to the current shell. New processes inherit unless overridden.
Soft vs hard limits
Each limit has:
- Soft limit: the actual enforced value. Processes can lower it further.
- Hard limit: the maximum value. Only root can raise it.
ulimit -Hn # hard limit (max)
ulimit -Sn # soft limit (current)
/etc/security/limits.conf
For per-user persistent limits:
# /etc/security/limits.conf
* soft nofile 1024
* hard nofile 524288
deploy soft nproc 4096
deploy hard nproc 8192
* is the default entry — it applies to users that have
no more specific entry. It is not a match-everything wildcard:
man 5 limits.conf states that group and wildcard limits are
not applied to the root user. Root needs its own lines,
spelled with the literal username:
root soft nofile 1024
root hard nofile 524288
Leaving those out is the usual way a host ends up with root running on the compiled-in kernel defaults while every other account gets the intended values — and root is the account running the daemons you were trying to tune.
The first field also accepts a group with @group, and a UID
range as min:max (@min:max for GIDs). Both are subject to
the same root exclusion.
Note the shape: a modest soft limit and a generous hard
limit. The hard limit is the ceiling a process is allowed
to reach; the soft limit is what it starts with. A process
that genuinely needs more descriptors raises its own soft
limit with setrlimit() up to the hard limit — no root, no
reboot, no login required. Handing every process a huge soft
limit by default gains nothing and breaks things.
PAM integration
The limits are applied by PAM at login:
# /etc/pam.d/common-session
session required pam_limits.so
The wording matters: at PAM session establishment. That
covers logins, su, and SSH sessions.
A systemd system service does not establish a PAM session
unless its unit sets PAMName=, so limits.conf never applies
to it. Whether that service’s user happens to have a login
session open elsewhere is irrelevant — the limits are attached
to the session, not to the user. This is the trap: an operator
raises nofile in limits.conf, confirms it with ulimit -n
in their own SSH session, and concludes the daemon got it too.
It did not.
Set service limits with systemd directives, and check the running process rather than your shell:
systemctl show my-service -p LimitNOFILESoft -p LimitNOFILE
grep 'open files' /proc/$(systemctl show -p MainPID --value my-service)/limits
Common production limits
# /etc/security/limits.conf
* soft nofile 1024 # low; apps raise it via setrlimit()
* hard nofile 524288 # ceiling apps may raise to
* soft nproc 16384
* hard nproc 32768
root soft nofile 1024
root hard nofile 524288
root soft nproc 16384
root hard nproc 32768 # never 'unlimited' for root
nofile (file descriptors) is the most common limit to
raise. Modern services handle many concurrent connections
and may need 65536+ file descriptors — so give them a hard
limit that allows it, and let them ask.
nproc (processes) is often hit by multi-threaded
applications. 16384 is a reasonable default.
Verify what a process actually got — the lesson’s other examples all set limits, none of them check:
# Live process
cat /proc/$(pgrep -f nginx | head -1)/limits
# What systemd will hand a unit
systemctl show nginx -p LimitNOFILESoft -p LimitNOFILE
# Current shell
ulimit -Sn; ulimit -Hn
/proc/<pid>/limits is the only authoritative answer.
ulimit -a in your shell tells you about your shell, not
about the daemon.
systemd overrides
For systemd services, prefer systemd’s directives over limits.conf:
[Service]
LimitNOFILE=65536
LimitNPROC=16384
These are more reliable for services.
Common pitfalls
- Limits don’t apply: the PAM module may not be loaded.
Check
/etc/pam.d/common-session. - Login session vs SSH session: limits.conf applies at PAM session start. SSH sessions that don’t go through PAM don’t get the limits.
- Docker containers: container limits are set by Docker,
not limits.conf. Use
docker run --ulimit nofile=65536 ....
Knowledge check
Knowledge check · 5 questions
Q1. What is the soft limit?
Q2. Raising nofile in limits.conf changes nothing for a daemon started by systemd.
Q3. Which of the following are valid ulimit types? Select all that apply.
Q4. After adding `root soft nofile unlimited` to limits.conf, several daemons started by root hang for minutes at startup or are OOM-killed before they log anything. What is the mechanism?
Q5. Setting `root hard nproc unlimited` is safe because root needs headroom.
Passing score: 75%. Answers are checked in this browser.