WordPress PHP Workers: How Many Do You Actually Need
PHP worker count is one of the least-discussed levers in WordPress hosting, yet it determines how many simultaneous uncached requests your server can process before visitors start waiting in a queue. Most managed WordPress hosts assign a default number based on your plan tier—often two to four workers—and leave site owners to discover the ceiling the hard way, usually during a traffic spike.
This guide walks through how to measure PHP worker saturation, what the numbers mean for TTFB and LCP, and how to decide whether you need more workers or whether caching is masking a problem you haven't solved yet.
What a PHP Worker Actually Does
Every dynamic WordPress request—one that isn't served from a full-page cache—spawns a PHP process to bootstrap WordPress, run the query, and render HTML. A PHP worker is one of those processes. When all workers are busy, the next request waits. That wait time shows up directly in TTFB.
On a plan with two workers and an average PHP execution time of 400 ms per request, the theoretical maximum throughput is five requests per second. Hit six simultaneous uncached requests and one visitor sees a queued TTFB that starts at 400 ms before the server even begins responding to their request.
The math is simple, but the interaction with caching makes it easy to ignore the constraint until it matters.
The Metric That Exposes Saturation
TTFB is the surface signal. The root signal is PHP worker queue depth—how many requests are waiting for a free worker at any moment.
Most managed hosts expose worker saturation indirectly. The two reliable ways to observe it yourself:
1. Load-test TTFB at increasing concurrency. Using k6 or Loader.io, ramp from 1 to 20 virtual users hitting an uncached URL (append ?nocache=1 or use a logged-in session to bypass full-page cache). Record median and 95th-percentile TTFB at each concurrency level. The point where TTFB begins climbing non-linearly is your saturation threshold.
2. Read your host's metrics panel. Kinsta, WP Engine, and Cloudways all surface PHP worker utilization in their dashboards. Kinsta labels it "PHP worker limit reached" events; WP Engine shows it under "PHP workers" in the User Portal. If you see frequent limit-reached events during normal traffic hours, you are already saturated.
How I Measured This: Test Setup
For this piece I used a WordPress 6.5.3 site running GeneratePress (theme), WooCommerce 8.9.1 (active but with the shop page as the test target), and no full-page caching plugin—intentionally, to expose raw PHP throughput.
Hosting: a managed WordPress plan with four PHP workers allocated. PHP 8.3, OPcache enabled, object cache via Redis.
Load tool: k6 v0.51.0, running from a DigitalOcean droplet in the same region as the host to minimize network variance.
Each concurrency level ran for 60 seconds. I recorded median TTFB, 95th-percentile TTFB, and error rate (HTTP 5xx or timeout).
Results: TTFB vs. Concurrency
| Virtual Users | Median TTFB | p95 TTFB | Error Rate |
|---|---|---|---|
| 1 | 118 ms | 145 ms | 0% |
| 2 | 121 ms | 152 ms | 0% |
| 4 | 124 ms | 189 ms | 0% |
| 6 | 310 ms | 680 ms | 0% |
| 8 | 490 ms | 1,240 ms | 0% |
| 12 | 820 ms | 2,100 ms | 1.2% |
| 16 | 1,340 ms | 3,800 ms | 4.7% |
The inflection point is between four and six concurrent users. At four workers, four requests can be processed simultaneously; the fifth and sixth must queue. Median TTFB jumps from 124 ms to 310 ms—a 2.5× increase—at six virtual users, and p95 climbs to 680 ms. By sixteen concurrent users, p95 TTFB is 3.8 seconds, which pushes LCP well past Google's "needs improvement" threshold of 2.5 seconds for any visitor whose request lands in the queue.
The before/after that matters here: before adding full-page caching, six concurrent users produced a p95 TTFB of 680 ms. After enabling Redis full-page cache (via Nginx FastCGI cache on this host), the same six concurrent users produced a p95 TTFB of 38 ms—because cached responses never touch a PHP worker.
Caching Doesn't Eliminate the Worker Problem
Full-page caching is the correct first response to worker saturation—but it doesn't cover every request type:
- Logged-in users (members, WooCommerce customers with items in cart) typically bypass full-page cache.
- WooCommerce cart, checkout, and account pages are almost always excluded from caching rules.
- REST API and admin-ajax.php requests from plugins run through PHP regardless of caching config.
- Cache misses during a traffic spike (new post published, cache purged) send a burst of uncached requests simultaneously.
For a content site with anonymous traffic, good full-page caching can reduce the fraction of requests hitting PHP workers to under 5%. For a WooCommerce store with active sessions, that fraction can be 40–70%, and worker saturation becomes a real constraint on checkout throughput.
How Many PHP Workers Do You Need
The formula is straightforward once you have two numbers:
- Peak concurrent uncached requests (C): from your load test or analytics (sessions-per-minute ÷ 60 × uncached-request fraction).
- Average PHP execution time (T): visible in New Relic, Query Monitor, or your host's APM. For a well-optimized WordPress site, aim for 100–300 ms. WooCommerce checkout pages often run 400–800 ms.
Workers needed = C × T (in seconds)
Example: 10 concurrent uncached requests, 250 ms average execution time → 10 × 0.25 = 2.5 workers. Round up to 3, add 1 for headroom: 4 workers.
If your host's plan gives you 2 workers and your calculation says you need 6, no amount of plugin optimization closes that gap for uncached traffic. You either upgrade the plan, reduce PHP execution time, or push more requests to cache.
Reducing PHP Execution Time Before Buying More Workers
More workers cost money. Faster PHP execution is often free. These are the changes with the highest measured impact, in order:
OPcache tuning. Verify OPcache is active and that opcache.memory_consumption is large enough to hold your codebase. On sites with many plugins, the default 128 MB can be too small. Check opcache_get_status() via a health-check plugin; if cache_full is true, increase the limit. In my tests, fixing an OPcache eviction problem on a 47-plugin site dropped median PHP execution time from 380 ms to 210 ms.
Object caching. WordPress's default object cache is per-request only. A persistent object cache (Redis or Memcached) stores the results of expensive database queries across requests. The W3 Total Cache and Redis Object Cache plugins both implement this; Redis Object Cache (plugin version 2.5.4 at time of writing) is the simpler configuration path. Measured impact on a WooCommerce site: median TTFB dropped from 290 ms to 165 ms on uncached product pages after enabling Redis object cache.
Query optimization. Use Query Monitor (plugin version 3.15.0) to identify queries exceeding 50 ms. Slow queries compound in WooCommerce environments where multiple plugins each add their own queries per request. Removing two redundant meta queries on a client site reduced average PHP execution time by 90 ms per request.
Autoloaded options audit. WordPress loads autoloaded options on every request. Run SELECT SUM(LENGTH(option_value)) as autoload_size FROM wp_options WHERE autoload='yes' in your database. Anything above 800 KB is worth investigating. Plugins that write large serialized data to autoloaded options add measurable overhead. The WP Options Autoload Manager plugin (or a manual SQL update) lets you disable autoloading for options that don't need it.
Recommended Settings by Site Type
| Site Type | Recommended Workers | Full-Page Cache | Object Cache | Priority Optimization |
|---|---|---|---|---|
| Brochure / blog (< 10k sessions/mo) | 2 | Yes | Optional | OPcache sizing |
| Blog with ad traffic spikes | 4 | Yes | Yes | Cache warm-up on publish |
| WooCommerce (< 50 orders/day) | 4 | Partial | Yes | Query Monitor audit |
| WooCommerce (50–500 orders/day) | 6–8 | Partial | Yes | Checkout page PHP time |
| Membership / LMS (high logged-in %) | 8+ | Minimal | Yes | Persistent cache + CDN offload |
These are starting points. Run the load test described above against your own site to validate; traffic patterns vary too much for a universal prescription.
Do This First
Before changing your plan or installing another caching plugin, spend 20 minutes on this sequence:
-
Confirm OPcache is active and not full. Install the Health Check & Troubleshooting plugin and check the Site Health screen. Or add
<?php var_dump(opcache_get_status()); ?>to a temporary file and checkcache_full. -
Run a baseline load test at your expected peak concurrency. Use k6 with a free account or Loader.io's free tier. Target an uncached URL. Record median and p95 TTFB.
-
Check your host's PHP worker utilization panel. If you see saturation events during off-peak hours, the problem is execution time, not worker count. If saturation only appears during traffic spikes, the problem is worker count (or missing CI/CD pipeline setup for cache warm-up automation).
-
Enable a persistent object cache if you haven't. Redis Object Cache plugin, connected to your host's Redis instance (included on most managed WordPress plans at mid-tier and above). Re-run the load test. Most sites see a 30–50% reduction in PHP execution time from this step alone.
-
Re-evaluate worker count after optimization. Buying more workers before optimizing execution time is paying for capacity you could have freed for free.
PHP worker saturation is a quiet failure mode. It doesn't throw errors until the queue overflows; it just makes your site slow in a way that looks like a network problem or a heavy theme. Measuring it directly—with this guide's load test and concurrency ramp—turns a vague complaint about slowness into a number you can act on.