PHP workers are the least visible and most misunderstood limit in WordPress hosting. A plan can advertise generous storage, unlimited visits, and a fast CPU, then fall over at 300 concurrent shoppers because the PHP pool is full. The site is not out of memory and the CPU is not pegged. It is out of concurrency.

This guide explains what a worker does, how to estimate the pool you need, how to observe saturation, and where managed hosts hide the limit. It is intentionally practical: the goal is a number you can put in a plan comparison, not a computer-science lecture.

What matters
  • A worker is occupied for the whole request. A 400 ms page holds a worker for 400 ms.
  • Queueing is non-linear. Above ~70% utilization, small traffic increases create large latency increases.
  • CPU is a lagging signal. p95 rises while CPU is still at 60% because requests are waiting, not computing.
  • Background jobs compete with checkout. Cron, imports, and subscription renewals consume the same pool.
  • More workers is not always better. Memory and database connections become the next bottleneck.

What a PHP worker actually does

On a conventional LEMP stack, Nginx receives the request and passes PHP files to PHP-FPM. PHP-FPM maintains a pool of child processes. Each child bootstraps WordPress, loads active plugins and the theme, runs the request, and returns the response. The child is busy for the entire time, including any database queries and outbound API calls.

A full-page cache can serve anonymous HTML without invoking PHP at all. That is why a cached homepage can handle thousands of requests per second on a small server. But a cart, checkout, login, admin page, or uncached template still needs a worker. On a store, the requests that create revenue are the ones that consume the limited pool.

The request path / where the queue forms
01 / EdgeCDN / cacheCached HTML returns without PHP.
02 / QueueNginx → FPMRequests wait when all workers are busy.
03 / WorkerPHP-FPMBootstraps WordPress, runs plugins.
04 / DataMySQL + RedisQueries, writes, object cache.

The queue between Nginx and PHP-FPM is where a site “hangs” without high CPU. Monitor it directly.

The concurrency math

Little's law gives a useful approximation: the average number of requests in the system equals the arrival rate multiplied by the average time each request spends in the system. In worker terms: concurrent requests = requests per second × average duration in seconds. Divide by target utilization to leave headroom.

# Example A: a content site with a warm page cache
# 40 dynamic requests/sec (uncached/admin), 0.25 s each, 70% target
workers = (40 * 0.25) / 0.70
# = 14.3 -> 15 workers

# Example B: a WooCommerce store at campaign peak
# 120 dynamic requests/sec, 0.40 s each, 70% target
workers = (120 * 0.40) / 0.70
# = 68.6 -> 69 workers

# Add a buffer for cron, imports, and forecast error
workers_with_buffer = 69 * 1.5
# = 104 workers

The formula is only as good as the inputs. Measure the request rate and duration from your own logs, not from a generic benchmark. Separate cacheable from dynamic requests. If 90% of your traffic is cached, the worker requirement is driven by the remaining 10% — plus the logged-in and checkout traffic that never hits the page cache.

Why p95 explodes before the pool is full

Queueing is non-linear. At 50% utilization, a request rarely waits. At 70%, waits are noticeable. At 85%, the tail grows quickly. At 95%, a small burst produces a long queue. This is why a host can look fine at 400 concurrent users and fail at 500: the system crossed the knee, and the next 100 users are waiting behind the first 400.

Averages hide this. If 95% of requests are served in 200 ms and 5% wait 2 seconds, the average may still look acceptable while real customers experience the tail. Report p95 and p99, and watch the queue depth, not just the mean.

The queueing curve

Latency grows slowly, then sharply, as utilization approaches 100%.

Stable Saturated
50% util.1.2×
70% util.2.3×
85% util.4.8×
95% util.9.5×
PWPH lab / queueing modelLatency relative to idle service time

How to see saturation

On a server you control, enable the PHP-FPM status page and scrape it. The key fields are active processes, idle processes, listen queue, max children reached, and slow requests. If active processes equal the pool size and the listen queue is non-zero, you are saturated.

; php-fpm.d/www.conf — status and slow log (example)
pm.status_path = /status
ping.path = /ping
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 2s

; Pool sizing (example only — tune to RAM and workload)
pm = dynamic
pm.max_children = 40
pm.start_servers = 8
pm.min_spare_servers = 6
pm.max_spare_servers = 16
pm.max_requests = 500

On a managed host, the panel may expose worker utilization or only a plan limit. Ask support what happens at the limit and how to monitor it. If the answer is “the site may return 502,” treat the worker count as a hard capacity constraint and load-test against it.

At the application layer, correlate p95 with query count and slow queries. A worker held for 800 ms by an unindexed query is a database problem disguised as a worker problem. Adding workers will push the queue into MySQL and make the incident worse.

How hosts limit workers

Worker models in the tested cohort / verify current plans
HostModelWhat to ask
KinstaPlan-based PHP worker allocationWorkers per plan, behavior at the limit, upgrade path
WP EnginePlan-based workers and visit tiersWhether visits or workers bind first; overage behavior
CloudwaysConfigurable PHP-FPM on your serverRAM per worker, safe max_children, database connections
Rocket.netManaged pool with plan limitsWorker ceiling and autoscaling behavior
PressableManaged platform limitsPlan concurrency and burst policy

Plan structures change. Confirm the current allocation and, more importantly, what happens when you exceed it. A queue with a slow response is different from an immediate 502.

WooCommerce: where workers go

A WooCommerce checkout is not one request. It is a sequence: cart update, shipping calculation, coupon validation, payment intent, order creation, and confirmation. Each step can consume a worker, and some steps call external APIs. A checkout that takes 1.2 seconds of worker time per order limits you to roughly 50 orders per minute with 1 worker, or 500 with 10 — before you account for catalog and admin traffic.

Add the background load: subscription renewals, failed-payment retries, abandoned-cart jobs, and order emails. These run through WP-Cron or Action Scheduler and use the same PHP pool. A renewal batch at 9 a.m. can starve checkout at 9:05 a.m. Move heavy jobs off-peak or to a separate runner.

Do not solve a database problem with workers. If the database is the constraint, more workers create more concurrent queries and a longer queue. Check slow queries, indexes, and write latency before adding pool capacity.

Tuning without creating a new problem

  1. Measure the current pool. Active processes, queue, max-children-reached, and slow requests over a full week.
  2. Size for memory. Estimate RAM per PHP process under your plugin load; leave room for MySQL, Redis, and the OS.
  3. Raise in steps. Increase max_children by 20–30%, then watch p95, memory, and database latency for 24 hours.
  4. Set max_requests. Recycle workers periodically to contain memory leaks; 300–1,000 is a common range.
  5. Enable the slow log. Find the request that holds a worker for 3 seconds; fix it instead of buying more workers.
  6. Separate background jobs. Run imports and renewals outside peak, or on a dedicated process where the platform allows.

The worker checklist

Before you blame the host
QuestionWhere to look
Are all workers busy?FPM status, active processes, listen queue
Is the database the real limit?Slow query log, write latency, connection count
Is a plugin holding workers?Slow log, profiling, query count per request
Is cron competing with traffic?Action Scheduler, WP-Cron events, queue depth
Is the cache actually working?Hit ratio, bypass rules, purge frequency
Is the plan limit a hard cap?Host docs, support, load test at the ceiling
A worker is a seat at the table. You can add seats, but if the kitchen is slow, everyone still waits.

Size for concurrency, watch p95, then check the cache.

Size the pool from concurrency, keep utilization below 70% at peak, separate background work, and watch p95 and queue depth rather than CPU. If you are buying a managed plan, ask for the worker count and the behavior at the limit. If you are running your own server, tune max_children against memory and database capacity, not against a generic recipe.

And before you buy more workers, check the cache. A site that invokes PHP for pages that could be cached is paying for concurrency it does not need.

PWPH Test Lab

Concurrency testing supports the load-test and hosting rankings. Read the methodology or the commercial disclosure.