The ticket usually reads something like this: “My site is constantly going down and coming back up.” Or “getting too much 503 errors.” Or “web server keeps going offline.” You reload and it works. You reload five minutes later and it’s a 503 again. Nothing in your control panel says anything is wrong.
We see this most weeks, and it is usually not an outage. A server that is genuinely down stays down. A site that flaps — fails, recovers, fails again, always under load — is hitting a ceiling. The useful question is which ceiling. And the part that trips up almost everyone: your resource report can show a clean bill of health while visitors are being served 503s. Both readings are correct at once.
There are two separate ceilings on the same request
If your account sits on a cPanel server running CloudLinux, a web request passes through at least two independent gates before your PHP code runs.
Ceiling A: your CloudLinux LVE (the account cage)
LVE is kernel-level enforcement around your whole account, and it is the real limit. The disk, bandwidth and addon-domain figures on a plan page are a different layer — they do not enforce CPU, memory or process counts. LVE has several knobs; these are the ones that produce flapping:
- SPEED — a CPU ceiling, as a percentage where 100% equals one core.
- EP (entry processes) — how many concurrent entries your account may have into the cage. Web requests are the usual ones, but cron jobs and SSH sessions count as entries too, which is why an EP wall can be hit in a quiet period if something scheduled is stacking up. This is the one that bites.
- NPROC — total processes alive at once: web workers, cron, shell sessions, everything.
- PMEM — physical RAM the whole account may hold.
- IO (disk throughput) and IOPS (operations per second) — two separate limits, faulted separately.
LVE also enforces VMEM (commonly disabled on modern configurations) and an inode limit; inode exhaustion breaks writes and uploads rather than causing flapping. Limits resolve as per-user override first, then LVE package, then server default — ceilings are set per account, not by plan name, which is why “my friend’s site on the same host is fine” proves nothing.
Ceiling B: your per-account PHP-FPM pool
If your domain is served by the PHP-FPM handler, it gets its own pool of PHP worker processes with its own cap on how many may exist — pm.max_children — plus settings like pm.max_requests (recycle a worker after N requests, which mitigates leaks). That ceiling lives in the web stack and is counted by the PHP-FPM master. Two gates in series, no shared accounting.
EP is how many visitors are allowed inside the building at once. The FPM pool is how many staff you have behind the counter. Hit the first and people are turned away at the door instantly. Hit the second and the queue at the counter grows until people give up.
These failures look different to a visitor
You can do most of this triage from a browser, with no server access, by watching how the failure behaves.
| What’s saturated | What the visitor sees | Timing signature |
|---|---|---|
| LVE entry processes (EP) | CloudLinux’s resource-limit page — stock version is HTTP 508, titled “Resource Limit Is Reached”, though hosts can replace it | Instant, no slow phase. Clears the moment concurrency drops. |
| LVE process count (NPROC) | A 500 or 503, with “fork: Resource temporarily unavailable” in your error log | Often not traffic-driven — stuck processes that never exited. |
| LVE memory (PMEM) | Blank page, truncated response, a plain 500, or a 503 | Instant, often only on one heavy page. |
| LVE CPU (SPEED) or IO throttling | Nothing fails; pages just take longer | Sluggish, sustained, no errors. The “slow but not down” report. |
| PHP-FPM pool | Slower and slower, then 502 or 503 | Progressive, bursty, time-correlated. Recovery is gradual. |
| Server-wide web server workers | Every site on the machine slows together | Not account-specific. Support-side. |
A 503 on its own does not identify the ceiling — PMEM, NPROC and pool exhaustion all produce one. What separates them is the LVE fault counters plus the timing: instant and self-clearing points at a hard per-account cap; slow-then-timeout points at a queue. That last column is the thing to write down before you open a ticket.
The zero-faults paradox
Here is the case that generated this article. A customer reported repeated 503s under load; their resource report showed zero faults, every LVE limit well inside allowance. Meanwhile the server logs were full of this (account name and configured number redacted):
WARNING: [pool <account>] server reached pm.max_children setting (N), consider raising it
(104)Connection reset by peer: AH01067: Failed to read FastCGI header
(11)Resource temporarily unavailable: AH02454: FCGI: attempt to connect to Unix domain socket /path/to/pool.sock failed
Both facts were true. LVE only records a fault when your account actually tries to exceed a limit. If the FPM pool cap is reached first, the pool refuses to start more workers — so the extra requests never enter the LVE cage at all. They sit in the pool’s socket backlog waiting for a worker. LVE sees an account running comfortably inside its allowance and records nothing, because nothing exceeded anything. The queue forms upstream of the meter.
The cause was a WordPress cache-preload plugin crawling the site against itself, overlapping with an Action Scheduler burst. Real visitor traffic was modest. The site was competing with itself for its own workers.
So a clean report is not proof of health — it only proves your account did not exceed its own cage, which narrows the search to the pool, the web server, the database or the application. The reverse exists too: faults climbing with no 5xx at all is CPU or IO throttling — slow, not broken.
Three memory limits, and they are not the same limit
PHP’s memory_limit applies per request and logs PHP Fatal error: Allowed memory size of N bytes exhausted. LVE PMEM covers the whole account; when it is hit, processes are killed inside the cage and you get a blank page, a 500 or a 503. The server’s own RAM is a third thing: when the kernel’s out-of-memory killer fires, processes die across accounts, so unrelated sites break at the same instant — that one is ours, not yours. If you tell support “I’m getting out-of-memory”, say which one you have evidence for.
What you can look at yourself
Availability varies by plan and server, so treat these as “check whether you have it”:
- cPanel > Metrics > Resource Usage — if this icon is present, it is the CloudLinux plugin: your usage against your LVE limits over time, with a fault count per limit type. That fault count is the single most useful number in this article. If the icon isn’t there, ask support for your usage figures.
- cPanel > Metrics > Errors — the most recent entries from your account’s web server error log, newest first, no SSH needed. It only holds a few hundred lines, which on an actively failing site can be minutes of history, so look while the problem is happening. File Manager gets you your logs directory and any PHP error log your app writes.
- cPanel > Software > MultiPHP Manager — per-domain PHP version, and on some servers a small PHP-FPM block (max children, max requests, idle timeout). It is frequently admin-only; if you don’t see it, ask support to resize the pool rather than hunting for the setting.
- cPanel > Software > Select PHP Version — on PHP-FPM domains this is cPanel’s own interface, and its Options tab is where
memory_limitandmax_execution_timelive. Some servers instead run the CloudLinux PHP Selector, which is not compatible with PHP-FPM: if the Selector holds a specific non-native version for a domain, PHP-FPM is not what serves that domain and the pool half of this article does not apply to it.
One more trap: php -v over SSH reports the command-line default, frequently not what runs your site. Put a file containing <?php echo phpversion(); in your document root, load it in a browser, then delete it.
For WordPress, the most useful thing you can enable yourself is debug logging — pointed somewhere private. In wp-config.php:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', '/home/USERNAME/logs/wp-debug.log' );
define( 'WP_DEBUG_DISPLAY', false );
WP_DEBUG_DISPLAY has to be defined alongside WP_DEBUG to reliably keep errors off the page. And do not set WP_DEBUG_LOG to plain true on a live site: that writes to wp-content/debug.log, a completely predictable public URL that routinely leaks absolute server paths, database errors and usernames, and that search engines have been known to index. Use a path outside your document root as above, and delete the file the moment you are done.
Not available to you: the PHP-FPM master’s own log, the server-wide error log, and server status pages. Those are support-side, and asking for them in a ticket is entirely reasonable.
The log lines, decoded
Where a line embeds a configured number we have replaced it with N — the string is what matters. Apache prints the errno as a prefix, before the AH code.
server reached pm.max_children setting (N), consider raising it— definitive proof the pool is the ceiling. Some builds omit thepm.prefix.[pool <account>] seems busy (you may need to increase pm.start_servers...)— the early warning, before the cap is hit.child <pid> exited on signal 9 (SIGKILL)— a worker killed, commonly by the memory cage.child <pid>, script '...' (request: "GET /...") execution timed out (Ns), terminating— gold: the request line names the URL that is hanging. On WordPress, very oftenadmin-ajax.phporwp-cron.php.(104)Connection reset by peer: AH01067: Failed to read FastCGI header— the pool took the request, then the worker died mid-flight.(2)No such file or directory: AH02454: FCGI: attempt to connect to Unix domain socket /path/to/pool.sock failed— nothing was listening. Errno 111 (connection refused) and 13 (permission denied) appear here too.(11)Resource temporarily unavailable: AH02454: FCGI: attempt to connect to Unix domain socket ... failed— the pool is there, but its backlog is full. The clearest single line proving the “slow, then 502/503” story. Where a pool listens on TCP instead of a socket file, the equivalent isAH00957.AH00484: server reached MaxRequestWorkers setting— the server-wide ceiling, not yours. Support territory.
What actually saturates a pool (it is almost never traffic)
What is rationed is worker-seconds, not requests. Twenty requests that each take four seconds burn 80 worker-seconds; four hundred requests at 100ms burn 40. Twenty times fewer requests, twice the pool consumption. Pool exhaustion is usually requests that take too long, multiplied by requests that should never have reached PHP at all.
- Cache preload plugins. A preload crawl fires many concurrent requests from your site at your own front end, each eating a pool worker and an entry process. Worst case is a scheduled preload that overlaps the previous run — the classic “site dies at the same time every night” report.
- Action Scheduler backlog (WooCommerce and anything built on it). Queued actions are drained by runners triggered over HTTP, so tens of thousands of pending actions become sustained self-inflicted concurrency plus heavy database load. Check WooCommerce > Status > Scheduled Actions; a pending count that only grows is the signal.
- wp-cron pileups. WordPress’s pseudo-cron fires on visitor page loads. If a scheduled task is slow, overlapping visitors each spawn another
wp-cron.phprun holding a worker for the full task duration. More traffic makes it worse, which is why it presents as a traffic problem and isn’t. - Uncached traffic. Crawlers on long-tail URLs, faceted-search permalinks, site-search URLs and anything with a cache-busting parameter all land on PHP. So do logged-in sessions and cart/checkout pages, cache-exempt by design — a store with many logged-in users runs almost entirely uncached.
admin-ajax.phpand the REST API. Heartbeat polling, page-builder autosaves, dashboard widgets. Several people editing in wp-admin at once can hold a large share of a small pool.- Slow database work. If your plan offers an object cache backend such as Redis or Memcached, WordPress will not use it until a drop-in is installed — look for
object-cache.phpinwp-content. MyISAM tables lock at table level; JOINs against unindexed columns serialise requests. All of it holds workers open. - High-diversity bot floods. A crawl spread across a very large number of source IPs, each hitting one uncacheable path and creating a new session, drains workers and database connections while no single IP looks abusive. Per-IP rate limiting is a poor fit; it needs a control acting on the aggregate, such as a challenge applied to that path at a CDN or WAF.
The single highest-value fix: kill pseudo-cron
In wp-config.php, above the require_once line that loads wp-settings.php — placed below it, the define does nothing:
define( 'DISABLE_WP_CRON', true );
Then in cPanel > Advanced > Cron Jobs, add a real scheduled task. Fifteen minutes suits most sites, if your plan permits that interval:
curl -sL --max-time 300 -o /dev/null "https://yourdomain.com/wp-cron.php?doing_wp_cron" >/dev/null 2>&1
--max-time matters: without it a hung task holds a worker indefinitely while the next run fires anyway, recreating the overlapping-job pileup you are trying to fix. -L follows the www and https redirects that otherwise make the command silently do nothing, and doing_wp_cron helps the request past page-cache layers. Drop to five minutes if you depend on scheduled publishing or transactional email, but every run costs a worker.
Server-side scheduled jobs can collide the same way — a backup and an update landing in the same slot. If a machine flaps on a fixed schedule, say so in the ticket; that is ours to check.
Who fixes what
| Action | You can do it |
|---|---|
| Disable pseudo-cron, add a real cron job | Yes |
| Reschedule or disable cache preloading | Yes |
| Clear an Action Scheduler backlog | Yes |
| Make more traffic cacheable; cut cache-busting parameters | Yes |
| Install an object cache drop-in; convert MyISAM; add indexes | Yes |
| Reduce Heartbeat frequency, trim admin-ajax pollers | Yes |
| Raise memory_limit / max_execution_time; change PHP version | Yes, up to whatever your account allows |
| Rate-limit or challenge abusive crawlers at a CDN or WAF | Yes |
| Edit the PHP-FPM block in MultiPHP Manager | Partial — admin-only on many servers |
| Any LVE limit: SPEED, EP, NPROC, PMEM, IO, IOPS, inodes | No — support-side |
| Resize the FPM pool, or change the PHP handler | No — support-side |
| Historical LVE fault data, FPM master log, server-wide ceilings | No — support-side |
How to file a ticket that gets fixed on the first reply
“My site is down” costs a round trip, because it does not identify a ceiling. This does:
- The failure mode. Instant resource-limit page, a 502/503 after a slow patch, or slow with no error. Paste the exact error text if you have it.
- The timing. Same time every night? Only in wp-admin? Only during a promotion? Only when the preload runs?
- What Resource Usage showed — specifically the fault counts, and whether they were zero.
Those three facts pick the ceiling, and picking the ceiling is most of the fix. If the report was clean and you are still getting 5xx, say so explicitly — that is not a contradiction, it is the signal that sends us to the pool instead of the cage.
Be First to Comment