2026-06-28
The scenario: a long-running daemon starts spitting too many open files. You can't restart it — it has state, sessions, in-flight requests. ulimit only affects the current shell. Editing the systemd unit requires a restart. /proc/<pid>/limits is read-only. Stuck?
Not if you know about prlimit, which hides in the util-linux package on every modern Linux box. It wraps the prlimit(2) syscall added in 2.6.36 — the one that finally let you read and modify the rlimits of an arbitrary PID.
Inspect a running process:
$ prlimit --pid 12345
RESOURCE DESCRIPTION SOFT HARD UNITS
AS address space limit unlimited unlimited bytes
CORE max core file size 0 unlimited bytes
CPU CPU time unlimited unlimited seconds
DATA max data size unlimited unlimited bytes
NOFILE max number of open files 1024 4096
NPROC max number of processes 7831 7831
STACK max stack size 8388608 unlimited bytes
...
Already nicer than grepping through /proc/<pid>/limits. But the real trick — change them live, no restart, no LD_PRELOAD, no gdb attach:
$ sudo prlimit --pid 12345 --nofile=65536:65536
The next open() call in that process just works. The daemon doesn't know anything happened.
It also doubles as an inline ulimit for new commands — useful for ad-hoc sandboxing without writing a systemd unit:
$ prlimit --as=1G --core=0 --nofile=4096 ./build.sh
Caps address space to 1 GB, disables core dumps, sets the open-file limit, then exec's the script.
Why this beats the alternatives:
ulimit — ulimit affects the current shell and its descendants. It cannot touch an already-running process. Period.prlimit is hot-patching.echo > /proc/<pid>/limits — that file is read-only. The kernel only exposes a setter through the prlimit syscall, which is exactly what this binary calls.setrlimit() — that only changes your own limits. You'd need prlimit() anyway, and at that point just shell out to the tool.A real production scenario: SSH onto a box where Postgres is logging "too many open files". cat /proc/$(pidof postgres)/limits shows NOFILE=1024. A restart drops every connection. Instead:
$ sudo prlimit --pid $(pidof -s postgres) --nofile=65536:65536
Bleeding stopped. Then go fix the unit file so it survives the next reboot.
The full list of limits it speaks: CPU time, file size, data, stack, core size, RSS, NPROC, NOFILE, locked memory, address space (AS), file locks, pending signals, msgqueue size, nice ceiling, RT priority, RT runtime. Almost all of those are essentially unreachable from userland unless you know prlimit exists.
Caveats worth knowing:
CAP_SYS_RESOURCE (usually root).open(); the process needs to retry./proc/sys/fs/nr_open — check that ceiling if your new value gets clipped.launchctl limit gets you partway there for launchd-managed daemons.ulimit manages your shell's children; prlimit manages any PID on the box — including the one you cannot afford to restart.
