← back to latest

See the resource limits every process inherits from your shell

ulimit -a

anatomy

ulimit
A shell builtin, not a program. There is no `/usr/bin/ulimit` to find, because a limit belongs to a process and an external command could only set its own. The builtin calls `setrlimit` on the shell itself, and every command the shell starts afterwards inherits the result through `fork`. That inheritance is the whole mechanism: you are not configuring a system, you are editing one process and letting its children copy it.
-a
Report every limit the platform supports instead of one. Each line ends with the option letter that reads or writes that limit, so the output doubles as the argument list: `open files (-n)` means `ulimit -n` queries it. Without `-a` and without any other option, bash assumes `-f`, so a bare `ulimit` prints the maximum file size and not, as most people expect, everything.
-S and -H
Pick which of the two values you mean. Every limit is a pair: the soft limit is what the kernel actually enforces, and the hard limit is the ceiling the soft one can be raised to. Neither flag is in the command above because reading defaults to soft, which is the number that will bite you. Writing does not default the same way, which is covered in the gotchas.
-n
Open file descriptors, the limit behind errno 24 and the message "Too many open files". It counts descriptors, not files: every socket, pipe, inotify watch, and epoll instance spends one. A process that opens 300 connections and 300 log files needs 600, which is why the number matters to network daemons far more than to anything reading from disk.

Sample output

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) 63515
max locked memory           (kbytes, -l) 8192
max memory size             (kbytes, -m) unlimited
open files                          (-n) 1024
pipe size                (512 bytes, -p) 8
POSIX message queues         (bytes, -q) 819200
real-time priority                  (-r) 0
stack size                  (kbytes, -s) 8192
cpu time                   (seconds, -t) unlimited
max user processes                  (-u) 63515
virtual memory              (kbytes, -v) unlimited
file locks                          (-x) unlimited

Two lines here decide most outages. open files (-n) 1024 is the ceiling your server hits at a thousand concurrent connections, and core file size (-c) 0 is why the crash you are trying to debug left no core dump behind. Everything printed is the soft limit, which is the enforced one; the hard limit sitting above it is usually much higher and costs nothing to claim.

When you would reach for it

A service dies at a suspiciously round number of connections. A crash you can reproduce refuses to leave a core file. A build works on your laptop and fails in CI with “Too many open files”, or a test suite that opens a lot of temporary files passes alone and fails when run in parallel. These are all the same question: what budget was this process handed at birth, and by whom. ulimit -a answers it for your shell, and by inheritance for everything you launch from it.

Gotchas

  • Setting a limit without -S sets both halves of the pair. ulimit -n 2048 looks like it lowers the soft limit and quietly drops the hard limit to 2048 as well. That is a one-way door: a non-root process can lower a hard limit but never raise it, so the next ulimit -n 8192 in that shell fails with “Operation not permitted” and the only fix is a new shell. Write ulimit -Sn 2048 when you mean the soft one, which is almost always.
  • Order matters when you lower a hard limit deliberately. If the new hard value would land under the current soft value, the kernel rejects it as EINVAL and bash reports “cannot modify limit: Invalid argument”, which reads like a permission problem and is not one. Lower the soft limit first, then the hard one.
  • Exceeding the file size limit does not return an error, it sends a signal. Write past -f and the process dies with SIGXFSZ and the shell prints “File size limit exceeded (core dumped)”. Code that carefully checks the return value of every write never sees it coming, and -f is measured in 1024-byte units in bash, so ulimit -f 1 means one kilobyte and not one byte.
  • Raising a limit in your shell does nothing for anything already running, including the service you are trying to fix. Under systemd the daemon’s limits come from LimitNOFILE= in its unit file and your login shell is not in the picture; on macOS the system ceiling is launchctl limit maxfiles and the default soft limit is 256, far lower than any Linux box. macOS also prints a different format, one line per limit with the letter first, as in -n: file descriptors 256.

Variants

$ for r in c f l n s t u v; do printf '%-3s soft=%-10s hard=%s\n' "-$r" "$(ulimit -S$r)" "$(ulimit -H$r)"; done

Both columns at once, which no single ulimit invocation prints. The gap between the two is free headroom you can claim with ulimit -S$r hard

$ prlimit --pid 1234

Read the limits of a process you did not start, Linux only, from util-linux. cat /proc/1234/limits gives the same pair of columns with no package required, and either one settles the argument about whether the running daemon actually picked up your config change

$ bash -c 'ulimit -Sn 64; exec ./import.sh'

Reproduce a descriptor exhaustion bug on purpose, in a subshell whose limits die with it. exec replaces the subshell so the script runs with no extra process in between, and your own shell keeps its original limit

lineage

Resource limits are a BSD idea grafted onto a System V interface. 4.2BSD introduced getrlimit and setrlimit in 1983 with the soft and hard pair intact, replacing the cruder vlimit call from 3BSD, and the C shell exposed them as the limit and unlimit builtins. System V went the other way: its ulimit was a system call of its own, narrow enough to be almost quaint, controlling the maximum file size a process could write and the break value, and the Bourne shell wrapped it in a builtin of the same name. The BSD interface was the better design and won, so ulimit the system call became a thin wrapper over setrlimit and then faded out, while ulimit the shell builtin grew an option letter for each new rlimit resource the kernel gained. POSIX standardized the builtin but only the parts that came from System V, which is why the standard guarantees -f, -H, and -S and nothing else, and why the letters everyone actually uses, -n above all, are extensions that each shell spells slightly differently. The output you get from ulimit -a is a sedimentary record: BSD semantics, a System V name, and forty years of resources bolted on one letter at a time until the alphabet started running out and bash moved to capitals.