ulimit: Understanding Resource Limits

ulimit: Understanding Resource Limits

What Is ulimit?

Conclusion: ulimit shows and changes the upper bounds on resources (open files, processes, memory, and more) that a shell and the processes it launches may use.

ulimit is a shell builtin (in bash and similar shells) that calls the kernel's getrlimit(2) / setrlimit(2) to read and set resource limits. A limit applies per process and is inherited by child processes created with fork.

The three common reasons to reach for it:

  • Diagnosing limit-exhaustion errors such as "Too many open files"
  • Raising limits so databases and web servers can handle their concurrency
  • Lowering limits to keep a runaway script from exhausting resources

Assumptions

  • bash is assumed (ulimit is a shell builtin; other shells like zsh support it with slightly different flags)
  • The persistence section assumes a systemd-based distro (Ubuntu / RHEL family)

Soft Limits vs Hard Limits

Conclusion: The soft limit is what is actually enforced; the hard limit is the ceiling for the soft limit. An unprivileged user can raise the soft limit up to the hard limit, but only root can raise the hard limit.

Every resource has two values.

Type Meaning What an unprivileged user can do
soft The currently enforced limit Raise or lower it, up to the hard limit
hard The ceiling the soft cannot exceed Lower only (cannot raise it)

Once you lower a hard limit, you cannot raise it again within that process tree (raising requires root, i.e. CAP_SYS_RESOURCE). In ulimit, flags select the target.

$ ulimit -Sn        # show the soft open files limit
$ ulimit -Hn        # show the hard open files limit
  • -S: act on the soft limit
  • -H: act on the hard limit
  • If omitted: display shows the soft value, but setting changes both at once — keep this in mind.

How Do I Check the Current Limits?

Conclusion: ulimit -a lists the current soft limit for every resource; use a single flag such as -n to inspect one resource.

$ ulimit -a
real-time non-blocking time  (microseconds, -R) unlimited
core file size              (blocks, -c) 0
data seg size               (kbytes, -d) unlimited
scheduling priority                 (-e) 0
file size                   (blocks, -f) unlimited
pending signals                     (-i) 15363
max locked memory           (kbytes, -l) 8192
max memory size             (kbytes, -m) unlimited
open files                          (-n) 1024
pipe size                (512 bytes, -p) 8
stack size                  (kbytes, -s) 8192
max user processes                  (-u) 15363
virtual memory              (kbytes, -v) unlimited

-a prints soft values. To see all hard limits at once, use ulimit -aH.

$ ulimit -aH        # hard limits for every resource

The Main Resource Types

Conclusion: In day-to-day work the four you touch most are -n (open files), -u (processes), -c (core), and -s (stack).

Flag Resource Typical use / symptom
-n open files "Too many open files." The file descriptor limit
-u max user processes "fork: retry: Resource temporarily unavailable"
-c core file size Whether a crash writes a core dump (0 disables it)
-f file size Maximum size of a single file
-s stack size Investigating segfaults from deep recursion
-v virtual memory Cap on the process's total virtual memory
-l max locked memory How much memory can be mlocked (relevant for DBs)

-u (max user processes) limits the process count for the whole user, not just this shell. It looks per-shell but is counted across every process owned by the same UID.

How Do I Change a Limit Temporarily?

Conclusion: Running something like ulimit -Sn 4096 affects only the current shell and processes it starts afterward; closing the shell reverts it.

# Raise the soft open files limit to 4096 (within the hard limit)
$ ulimit -Sn 4096

# Verify
$ ulimit -Sn
4096

Asking for more than the hard limit is rejected.

$ ulimit -Sn 999999
bash: ulimit: open files: cannot modify limit: Operation not permitted

That requires raising the hard limit, which an unprivileged user cannot do. As root you can change both at once:

# root only. Set soft and hard together to 65536
$ sudo bash -c 'ulimit -n 65536; exec your-server'

A ulimit change reaches only the shell that ran it and its descendants. It does not change the limits of an already-running daemon. To apply limits to a live process, use prlimit.

How Do I Make a Limit Persistent?

Conclusion: For login sessions use /etc/security/limits.d/*.conf; for systemd-managed services use the unit's LimitNOFILE=. They are applied through different paths.

Login shells (via pam_limits)

/etc/security/limits.conf and /etc/security/limits.d/*.conf are applied at login by the PAM module pam_limits. This covers shells started through SSH login or login.

# /etc/security/limits.d/90-nofile.conf
*        soft    nofile    8192
*        hard    nofile    65536
deploy   soft    nproc     4096

The format is <domain> <type> <item> <value>, where domain is a username, @groupname, or * for everyone. Changes take effect only after you log out and log back in.

pam_limits does not affect processes that do not go through a PAM session. limits.conf does not apply to daemons started by systemd, which is a common reason a "raised" limit appears to have no effect.

systemd services

For systemd-managed services, set the limit in the unit's [Service] section.

# /etc/systemd/system/myapp.service.d/override.conf
[Service]
LimitNOFILE=65536
LimitNPROC=4096
$ sudo systemctl daemon-reload
$ sudo systemctl restart myapp

To change the default for all services, edit DefaultLimitNOFILE= in /etc/systemd/system.conf. Check the effective value with systemctl show myapp -p LimitNOFILE.

How Do I Inspect a Running Process's Limits?

Conclusion: Read /proc/<PID>/limits or use prlimit --pid <PID>. prlimit can also change a running process's limits without a restart.

$ cat /proc/1234/limits
Limit                     Soft Limit  Hard Limit  Units
Max open files            1024        524288      files
Max processes             15363       15363       processes
...

prlimit checks and changes limits in one line.

# Inspect
$ prlimit --pid 1234 --nofile

# Change a running process's soft/hard to 8192 (may need privileges)
$ sudo prlimit --pid 1234 --nofile=8192:8192

# Launch a command with a limit applied
$ prlimit --nofile=4096:4096 ./myserver

Fixing "Too many open files"

Conclusion: Count the actual file descriptors with lsof and compare them against the limit. Decide whether it is an FD leak or a genuinely insufficient limit before acting.

"Too many open files" means the open files (-n) limit was reached. Work through it as follows.

  1. Identify the PID of the affected process
  2. Check its effective limit (cat /proc/<PID>/limits)
  3. Count how many descriptors it currently has open
# Count the FDs a process has open
$ lsof -p 1234 | wc -l
  1. If the count is pinned at the limit, decide the cause.
  • FD leak (descriptors opened but never closed) → fixing the application is the real solution; raising the limit only buys time
  • Legitimately high (heavy concurrency) → raise the limit using the persistence steps

Summary

  • ulimit manages per-process resource limits; keep the soft/hard two-tier model in mind
  • Change temporarily with ulimit -Sn N; inspect with ulimit -a, /proc/<PID>/limits, or prlimit
  • Persist via limits.d/*.conf for logins, or the unit's LimitNOFILE= for systemd services — do not mix up the paths
  • For "Too many open files," suspect an FD leak before raising the limit

Next Reading