VPS "fork: Resource Temporarily Unavailable": Fix nproc Limits

· 6 min read · 5 views · Getwebup

You SSH into your VPS to restart a service and get slapped with -bash: fork: retry: Resource temporarily unavailable. Or a customer's PHP script that used to work fine suddenly can't spawn a subprocess. Nothing in top looks unusual — CPU is fine, RAM has headroom, disk isn't full. You've hit a completely different ceiling: the number of processes a single user is allowed to run at once.

Symptom: "fork: retry: Resource temporarily unavailable"

This one shows up in a handful of disguises depending on what triggered it:

  • Logging in over SSH gives fork: retry: Resource temporarily unavailable or the session just hangs and drops
  • A cron job fails silently, or its log shows -bash: fork: Cannot allocate memory (misleading — it's not actually a memory problem)
  • PHP-FPM or a Node app throws EAGAIN when trying to spawn a child process
  • WHM/cPanel shows the account hitting its "Number of Processes" limit in the Resource Usage graph, or CloudLinux logs an LVE nproc hit
  • Running ps -u yourusername | wc -l returns a number suspiciously close to a round figure like 100, 512, or 1024

The confusing part is that free -m and df -h both look healthy. That's the tell: this isn't memory or disk, it's a process-count limit.

Cause: you've hit the nproc limit, not the open-files limit

Linux enforces several independent ceilings per user, and it's easy to confuse them. If you've read our post on too many open files errors, this is the sibling problem — same family of controls, completely different resource:

LimitControlsCheck withTypical error
ulimit -nOpen file descriptors (sockets, log files, pipes)ulimit -nToo many open files, EMFILE
ulimit -uNumber of processes/threads a user can runulimit -ufork: retry: Resource temporarily unavailable
Kernel pid_maxTotal PIDs across the whole servercat /proc/sys/kernel/pid_maxServer-wide fork failures, rare
CloudLinux LVE nprocPer-cPanel-account process capWHM → Edit LVE / lveinfoAccount-specific hangs, other accounts unaffected

On a plain Ubuntu/Debian/AlmaLinux VPS, the process limit comes from PAM's limits.conf mechanism, plus a per-service override if the process was started by systemd. On a cPanel VPS running CloudLinux, there's a second layer on top: each cPanel account runs inside an LVE (Lightweight Virtual Environment) with its own nproc ceiling, independent of the OS-level limit.

The usual triggers:

  • A WordPress site with a runaway cron (WP-Cron firing hundreds of overlapping requests because a previous run never finished)
  • A backup script or import job that spawns a subprocess per file instead of reusing one
  • A PHP-FPM pool set to a huge pm.max_children that's technically legal for the OS but blows past the cPanel account's LVE nproc cap
  • An actual fork bomb — a compromised script or a malicious cron doing :(){ :|:& };:-style recursive forking
  • Node.js or Python worker pools (Celery, PM2 cluster mode) configured with more workers than the box was sized for

Fix: find what's forking, then raise (or correct) the limit

1. Identify who's eating your process budget

Before touching any limit, find out what's actually running:

ps -eLf | awk '{print $2}' | sort | uniq -c | sort -nr | head -20
ps -u www-data --no-headers | wc -l
ps -u yourcpaneluser --no-headers | wc -l

The first command counts threads/processes grouped by parent PID so you can spot which one is spawning hundreds of children. The next two count processes owned by a specific user — on cPanel, run the second one as the account's own user via su - accountuser -c "ps -u accountuser | wc -l".

2. Check the current limit

ulimit -u
cat /etc/security/limits.conf
ls /etc/security/limits.d/

For a systemd-managed service (Nginx, PHP-FPM, MySQL), the PAM limit is often irrelevant — systemd sets its own via DefaultTasksMax and per-unit TasksMax:

systemctl show php8.2-fpm | grep -i tasksmax
systemctl show -p DefaultTasksMax

3. Raise the limit properly

For a shell user or an application account on plain Linux, add a drop-in file rather than editing limits.conf directly (easier to track and remove later):

cat > /etc/security/limits.d/90-appuser-nproc.conf <<EOF
appuser soft nproc 4096
appuser hard nproc 8192
EOF

Log the user out and back in for it to apply — ulimit -u changes don't retroactively apply to an already-open session.

For a systemd service, don't touch PAM limits at all — override the unit instead:

systemctl edit php8.2-fpm.service
[Service]
TasksMax=2000
systemctl daemon-reload
systemctl restart php8.2-fpm

4. On cPanel/CloudLinux, raise the LVE nproc cap

If ulimit -u looks generous but the account still stalls, the LVE is the actual bottleneck. In WHM:

  • Go to WHM → Edit LVE and search for the account
  • Increase the Number of Processes (NPROC) value — a typical shared-hosting default is 20–25, which is far too low for a WordPress site running WP-CLI, cron, and PHP-FPM workers concurrently
  • Confirm the change took effect from the account's own shell: lveinfo $(whoami) or check WHM → Resource Usage

Don't just max this out for every account, though — a genuinely runaway process on a shared box will happily consume whatever ceiling you give it and starve its neighbours. Raise it enough to cover legitimate peak usage, not to make the symptom disappear forever.

5. If it's a fork bomb or malware, kill it before raising anything

ps -u accountuser -o pid,ppid,cmd --sort=start_time | tail -50
kill -9 <offending PID>
# or, to nuke everything owned by one user in an emergency:
pkill -9 -u accountuser

Then check crontab, wp-content, and any recently modified PHP files for the thing that spawned it — raising the nproc limit on top of active malware just buys the attacker more headroom.

Prevention

  • Cap PHP-FPM sanely. Set pm.max_children based on available RAM per worker, not an arbitrary big number — this keeps you from ever approaching the LVE cap under normal load.
  • Fix overlapping WP-Cron runs. If a site's cron takes longer than the interval between visits, disable the default WP-Cron and drive it from a real system cron job once a minute instead — it prevents the pile-up that eats process slots.
  • Monitor process counts, not just CPU/RAM. Add a simple check to your monitoring (Netdata, a cron + mail script, whatever you already run) that alerts when a user's process count crosses 70% of its limit.
  • Review batch scripts. Any script that does system(), exec(), or spawns a subprocess per row/file should reuse a small worker pool instead of forking once per item.
  • Set per-service TasksMax deliberately on general-purpose VPS boxes so one misbehaving service can't starve the others even before the OS-wide ceiling is reached.

FAQ

Questions people actually ask