I know that error is infuriating — especially when the machine has plenty of RAM and CPU sitting idle. The good news: on Linux, EAGAIN ("Resource temporarily unavailable") is nearly always a limit you can raise in under two minutes.
Find out which limit is biting you
Run these four commands in order. Whichever one returns a suspiciously low number is your culprit.
# 1. Per-user process/thread cap for the current shell
ulimit -u
# 2. Kernel-wide thread ceiling
cat /proc/sys/kernel/threads-max
# 3. Your user's current thread count
ps -u $(whoami) -L | wc -l
# 4. If you're in a container or systemd unit, check the cgroup pids cap
cat /sys/fs/cgroup/pids.max 2>/dev/null || cat /sys/fs/cgroup/pids/pids.max
The fix, by culprit
If ulimit -u is low (classic case)
On a default Ubuntu 22.04 login shell this is often 62788, which sounds like a lot until you realize every thread counts. Java apps eating 500 threads, Node worker pools, and nginx workers add up fast.
Raise it for the current shell:
ulimit -u 65535
Make it stick for a service by editing its systemd unit:
# /etc/systemd/system/myapp.service
[Service]
LimitNPROC=65535
LimitNOFILE=65535
Then reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart myapp
If pids.max is capped (containers)
Docker's default --pids-limit is unlimited, but Kubernetes pods often inherit a container runtime default of 1024 pids. A JVM with 500 threads and a sidecar will blow through that instantly.
For Docker, restart with the flag:
docker run --pids-limit=8192 myimage
For systemd services, set TasksMax:
[Service]
TasksMax=8192
If threads-max is the wall
On machines with a lot of RAM this is usually huge (e.g. 126,000+), but embedded boxes with 512MB RAM can show 6,000. Nudge it up temporarily:
sudo sysctl -w kernel.threads-max=50000
Persist it in /etc/sysctl.d/99-threads.conf:
kernel.threads-max = 50000
Why this works
EAGAIN from fork() or pthread_create() means the kernel refused to allocate a new task — not that memory ran out. Four separate counters gate that allocation: the per-user RLIMIT_NPROC, the kernel's threads-max, the cgroup pids controller, and occasionally vm.max_map_count when each thread maps a large stack.
The trick is that hitting any one of them returns the same errno. That's why "just add more RAM" doesn't help — the kernel was never short on memory. You have to find the specific ceiling that closed.
Less common causes
vm.max_map_count: A JVM or Elasticsearch thread allocating a big stack can fail here. Check withsysctl vm.max_map_count; if it's 65530, raise it to 262144 (Elasticsearch's recommendation for ES 8.x).- PID namespace exhaustion: In Kubernetes, the node's PID space can fill even when your pod is fine. Check with
kubectl get --raw /api/v1/nodes/<node>/proxy/stats/summaryor just look atkubectl top nodesfor process spikes. - Memory cgroup OOM-adjacent behavior: If
pids.maxandulimitlook fine butforkstill fails, rundmesg | tail. A cgroup v2 memory.max hit will sometimes surface as EAGAIN before OOM-kill fires. - sshd MaxStartups: If you're seeing this only on SSH logins for many users at once, it's not fork — it's sshd's
MaxStartups(default 10:30:100). Bump it in/etc/ssh/sshd_config. - AppArmor/SELinux denials: Very rare, but
dmesg | grep -i deniedwill tell you if a profile is blocking clone().
Prevention
Three habits keep this off your plate:
- Set
LimitNPROCandTasksMaxexplicitly in every long-running systemd unit. Don't rely on distro defaults — they vary wildly between RHEL 8 (default 4096 tasks) and Ubuntu 22.04. - For anything containerized, pin
--pids-limitat 4x your expected thread count. A rough rule: JVM heap in GB x 200 + 200 buffer. - Alert on
node_processes(node_exporter) and per-pod process counts. You want the page beforeforkstarts failing, because once it does, the app usually can't even log the error.
If you've checked all four gates and still see EAGAIN, drop the exact command and the output of ps -eLf | wc -l in a comment — that combination narrows it to a handful of causes fast.