PHP workers are getting a bad rap lately in the WordPress hosting world. Most commonly, they’re being cited as a scapegoat for a hard upsell by the host. Let’s dig in a bit deeper and discover the truth behind PHP workers and how they apply to hosting WordPress sites.
There you are, just minding your own business – literally minding your online business – when your workflow is suddenly interrupted with a prolonged loading page.
You say to yourself, “That’s weird. It seemed to be working fine just a moment ago”, so you tap the refresh button. After a moment, you see a white screen with a 50x error printed across the top. You want to be sure that it wasn’t just a freak accident, so you give that refresh button one last tap and the WordPress admin page you were working on loads right up. It’s like nothing ever happened.
Since the incident, a million thoughts of panic and discomfort are running through your mind. “Was this just a random issue? Should I worry about this? How often does this happen? Do my customers see this too?”
Unfortunately, there’s no magical catch-all answer. This “blip” or “timeout” could be attributed to any number of causes throughout your hosting stack.
Within this article, we are focusing on the specific and casually offered, “you need more PHP workers” reframe that far too many WordPress hosting support desks seem to respond with.
A single-core processor on a timeline. 2 PHP workers drop tasks into the processing queue and are handled in a fairly consistent fashion. Memory usage stays somewhat constant as CPU usage modulates with workloads.
In a basic sense, a PHP worker is a background computing process that runs PHP code. It hangs out on the server, waits to be told what to do, does it (hopefully in a timely fashion), returns the response, then goes back to twiddling its thumbs until another task comes in.
When they pick up a new task, they’ll use whatever CPU cycles and RAM they have available to get the job done. To ensure that they always have resources available for when the next job comes in, PHP workers also reserve some resources to stay warm after they finish.
Of course, you wouldn’t want to have a bunch of PHP workers sitting around and eating up additional resources just to sit there idle. In addition to being statically defined by always keeping the same number of PHP workers ready, they can also be dynamic. By utilizing dynamic PHP worker counts, they’re able to grow or shrink as necessary.
In a WordPress context, PHP workers are kind of dumb. Since WordPress runs as a single-threaded application, a single PHP worker handles the entirety of the request. Because of this, a typical WordPress request looks something like the following:
- A request comes into the web server and passes it off to PHP.
- A PHP worker within the worker pool starts processing the code.
- If your code requires information from the database, the PHP worker then passes a query over to the database server, then waits to get the results back.
- Once the worker receives what it needs from the database, it then performs further processing before finishing and passes the completed work back over to the webserver.
Most of the time, you don’t need to know or even care about how many PHP workers your hosting account has, much less how they are allocated – until you run out.
When high traffic to your site, poor performing code, or a combination of the two uses up all your available PHP workers, it bottlenecks the chain. Once you run out, requests get turned away or queued until one of your PHP workers finishes a task and can take on another one.
As we stated above, a PHP worker stays “blocked” while active. During that time, it’s not able to perform any additional tasks until the current task has finished. The benefit of having more PHP workers is that it may allow the site to handle more tasks simultaneously. Typically, the number of PHP workers is not as critical as the type of workloads that those workers are performing.
While handling more tasks simultaneously may help with making sure that every task gets finished, excessive PHP workers can also cause individual tasks to take longer. As each task takes longer to finish, this means that when resources are exhausted, it can take quite a while for spare PHP workers to become available.
Typically, the number of PHP workers is not as critical as the type of workloads that those workers are performing
An excellent way to think of your worker queue is like a list of tasks. If you have a few tasks that you can handle at once, you can get them all finished relatively quickly and become ready for more tasks. But if you take on too many tasks at once, you might find yourself overextended, take longer to complete each task, and see your backlog grow exponentially.
Code performance is the most significant factor in regards to PHP Worker capacity.
Let’s take a look at a very simple instruction in PHP:
print(“This is a PHP function”);
This instruction alone occurs almost instantaneously. Because it happens so quickly, it consumes 1 worker for only a brief nanosecond.
On the other extreme, let’s look at a complex instruction that could occur when a customer searches for a product on an eCommerce site. If the customer was searching for car parts that are compatible with a particular product, your code might handle it like this:
- Give me all products from my database that match the keyword “spare part”, has a price between $5 and $15, and is compatible with SKU “4232”.
- Now take these records and process them. For each one, get the paths for up to 3 corresponding image thumbnails.
- Before making the items available, make a call to my 3rd-party inventory management system API and check to see how many are available.
- Once you have all of the products and inventory counts, start building the web page with product reviews, social media posts, and the product images for each product.
- Print that content to the browser along with my header, footer, and sidebar.
Relatively speaking, this process keeps 1 worker occupied for a very long time. Each task waits for its part of the instructions to finish before it can move on to the next. Combine this with that page builder plugin that you love so much to generate the 3MBs of HTML, and you’ll be left with a reasonably substantial PHP process to chew through.
Now, if your database is slow for any reason, it’s going to take a few hundred milliseconds longer to return its data, keeping that PHP worker active while waiting.
Let’s say that you’re hosting your product inventory management system at a different cloud provider – the PHP worker needs to wait for that API communication and transaction to take place.
You get the idea. The worker has to finish a task before it can take on any more tasks. Most of the time, it’s a non-issue. It does its processing, your page renders in 300-600ms, and you move on with your life.
2 PHP workers drop tasks into the CPU timeline. They’re processed consistently at first, but then 1 task “waits” as the other worker tasks continue moving. This demonstrates how a worker can become occupied or “stuck” while the transaction takes place, unable to do anything else until the task completes.
On your typical WordPress site, the whole process works fine a million times a day.
Except when it doesn’t.
Let’s say that your marketing team just sent out a wildly successful newsletter blast. It drove an influx of traffic to your site, and the crew is popping champagne in the break room.
But on your server, it’s a different story entirely. You’re getting the same types of requests, but they have now multiplied and are happening all at once. From there, your PHP workers get bottlenecked as they cannot clear their tasks fast enough. That’s when your site slows to a crawl.
Most modern hosting stacks use PHP-FPM to manage the number of PHP workers, allowing tasks to queue up gracefully while waiting for CPU resources to become available.
If you are running Apache and mod_php, request queuing won’t happen, and any new request is unceremoniously dumped. Queuing is an excellent solution to handling short bursts of traffic, but has a downside that becomes apparent when your sustained traffic occurs at a higher level than you can process.
When every request spends time in the queue, all you are doing is adding additional time to the response. If the queue size is large enough, you can even get into a scenario where each request is waiting for greater than 30 seconds before work even starts. At this point, you aren’t completing any requests in a useful amount of time. You would have been better off serving errors to half of the traffic and content to the other, rather than make everyone wait half a minute or more.
4 Workers drop tasks into the CPU timeline, which are processed in a fairly consistent fashion at first, but then begin to queue up waiting for available CPU.
In summary, the PHP-FPM request queue lets you use the optimal number of PHP workers while still handling traffic bursts. However, it still needs to be capped at a size that allows it to process the entire queue in a short amount of time. Otherwise, you could end up in a runaway delay situation.
As we’ve previously mentioned, each PHP Worker consumes a bit of RAM and CPU cycles. Even if you have 50 PHP workers to handle your workloads, your run-of-the-mill WordPress hosting plan likely does not have enough resources allocated to run all of those workers efficiently.
One cannot merely add more PHP workers in a vacuum.
Increasing capacity to serve additional website requests means increasing CPU and RAM (server/container size) in proportion to the PHP workers, based on what your workloads demand.
4 Workers drop tasks into the CPU timeline. As the volume of tasks increases and all available memory is occupied, tasks are unceremoniously killed off to make room for the tasks coming in behind it. This is a demonstration of the Out of Memory (OOM) task killer which, when active, causes all sorts of unpredictable performance issues.
The 4 variables of CPU, memory, PHP worker count, and workload (code efficiency) all need to be in harmony when it comes to right-sizing a hosting stack. It’s worth mentioning again that code efficiency is the most important. Increasing the other 3 may just let you do more of the slow and inefficient tasks (concurrency) with no effect on how fast those tasks complete (performance).
Caching, of course, helps alleviate many of the bottlenecks described above.
Caching at the database layer with native MySQL query caches or a Redis Object Cache, saves the PHP worker from having to communicate with the database and wait for a response.
Caching further up the stack in the webserver prevents the PHP worker from even being asked to do anything. Unfortunately, it’s not always possible due to session cookies or dynamic requests.
Caching responses from 3rd party APIs, like the inventory management system that we described earlier, reduces that “waiting” time as well.
The more efficiently you can cache and still meet your business goals for the website, the less PHP work needs to be done, which means an overall smaller demand on server resources.
There are several benchmarks you can view online, and they all say the same thing: PHP 5 is faster than 4, 7 is faster than 5, and 7.3 is faster yet again than 7.2. You get the idea.
You may not have any control over what version of PHP your host provides, but if you do, always opt for the latest version. The speed (and security) gains of newer PHP versions are real and quantifiable.
There is no universal answer to this question as each WordPress site and workload is different.
Generally speaking, you want to tune the number of PHP workers to consistently use 80-100% of your available CPU capacity. Complex workloads may only allow for 2 PHP workers before consuming all available CPU. In contrast, efficient and optimized workloads may allow for 4, 6, or 8 workers per core with identical server specs.
Too many workers for the available CPU simply makes everything slow down as they get queued up, and the CPU spends much of its time switching between tasks, rather than getting work done.
Too few PHP workers for the available CPU wastes resources, since some percentage of your CPU remains idle, rather than getting work done.
A good optimization of workers to CPU cores. 4 PHP workers leverage 2 CPU cores and have ample memory to process each task efficiently with a little (but not too much) buffer before maxing out.
Tuning PHP workers is a fairly straightforward process as long as you follow a few basic rules:
- You want to run as few workers as possible.
- You should have a hard limit on the number of PHP workers based on the memory used by each worker, and the amount of RAM on your server – this is very site-specific, as each website may have a different usage profile.
- Once your CPU is at 100% utilization, adding more workers simply makes each task slower.
- If your worker CPU usage is low, but your requests are still slow, your bottleneck is in something PHP is making a request to (Database/Cache/API). Increasing the number of simultaneous requests to that component might make it slower, so you have to monitor it.
An excellent question to consider is this: why are you trying to tune your PHP workers? Is it maximum concurrency or maximum performance?
At Pagely, we always try to understand the customer’s business goals when recommending which “hosting plan” to use. From there, Pagely tunes each plan to the specific customer’s needs. Dozens of small sites have a very different workload footprint than 1 large site on the same spec hardware.
With lots of sites, you have two main worries: memory overhead and noisy neighbors (this can include just your own sites on the same VPS).
Memory overhead is mostly static, based on the average memory usage of each site. For simple brochure-style sites, you might plan for 128-256MB of RAM for each site (plus some fixed OS overhead, depending on your stack). At your average cloud provider, you are going to be looking at instances with a higher memory-to-core-count ratio on the smallest size. As you scale, you’ll then switch to a lower memory-to-core-count ratio.
As for PHP workers, you are going to want to use dynamic worker limits, generally with a minimum of 1 PHP worker to reduce latency and setting a maximum that only uses 25% or 50% of the server CPU. Setting these parameters lets a couple of sites burst at simultaneously, without causing performance issues on the other sites. Each site gets access to the CPU, but no one site can consume all of it at once.
Setups like this tend to have a ton of unpredictability, so you are better off focusing on stability in worst-case scenarios instead of maximum performance and efficiency for any single site.
With a single site, you can match the hardware and worker counts to get the most bang for your buck. You will almost always want to use cloud instances that are optimized for computing power (number and speed of CPU cores). From there, you should use either a fixed number of PHP workers or a dynamic configuration with a high minimum number of workers.
If your database is running on another server, a good starting point is to take the number of CPU cores and multiply it by 2.
Increase workers-to-CPU-cores when:
- You are making a lot of database or API queries.
- Your average response time is minimal (< 100ms).
Decrease the workers-to-CPU-cores ratio when:
- Your codebase has low efficiency
- You aren’t making many database or API queries
- You are generating very large responses
This is our least favorite solution – and one that we only recommend after we’ve gone through everything else.
Sometimes it just has to work, right now, and refactoring poor performing code is time-consuming and not always feasible. Throwing hardware at the problem usually works, but with a major downside – it gets really expensive, really fast. You can typically increase concurrency and handle more tasks, but those tasks will never get any faster until the code efficiency is addressed. As our CTO says, “it just allows you to do more of the slow things”.
We get it – several of the hosting companies have trained the consumer base to be concerned about the “number of PHP workers” and to think that number has some magical effect on how their WordPress site performs.
We also understand that many of these same WordPress hosting companies have successfully brainwashed everyone to concern themselves with “number of Pageviews” as well.
(Pageviews as a bogus billing metric is a topic for another blog post entirely.)
These are both the wrong questions to ask when shopping for a scalable and reputable host for your WordPress website.
The question you should be asking is simple:
Does this host have the skill and knowledge to configure my plan specifically to my needs so that I can get the most bang for my buck (performance to value)?
If they keep pushing plan upgrades and telling you that “you need more PHP workers” without any background analysis offered, you already have the answer you need.
We’ve run a full analysis of the impact of PHP workers, including all of the source code for our tests. If you want to take a deeper dive into our data, a full write-up is available within our PHP worker benchmarking and analysis article.