PHP-FPM works by spinning up some PHP processes to run your application—each process can only handle a single request at a time, so if you need to handle 50 concurrent requests, PHP-FPM must be running at least 50 processes. Without enough available processes, PHP-FPM will have to queue the requests and wait until some become free to handle them.
With this configuration, PHP-FPM dynamically starts and stops processes to handle increased or decreased load, but you need to provide hints and sane limits for it. It’s possible to configure a static number of processes that will never change, but it’s not worth covering. The dynamic process manager is very robust and handles both high and low load situations extremely well. The configuration variablepm.max_childrencontrols the maximum number of FPM processes that can be run at the same time. Effectively, this setting controls the maximum number of requests that each application server can handle. It should be tuned depending on the memory footprint of your application and the amount of total memory on the server. A good place to start is using this formula:
App Server: Horizontal Scaling with Nginx and PHP-FPM 33 1 pm.max_children = (MAX_MEMORY - 500MB) / 20MB
For example, if your application server has 4GB of memory, you’d want to set pm.max_children at 180, allowing PHP-FPM to use a maximum of roughly 3.5GB of memory. Remember—you want to leave some memory for nginx to use, as well as any other services you may have running on the system.
The worst thing you can do is setpm.max_childrentoo high! Under heavy load, PHP-FPM could very possibly require more memory then is available and begin to swap, likely crashing the server or grinding it to a halt.
pm.start_serversis the number of PHP-FPM children that are forked when it first starts up. I recommend 10% of your maximum capacity.
pm.min_spare_serversis the minimum number of unused PHP-FPM children that should hang around at any given time. For example, ifpm.min_spare_serversis set to 5 and you have 40 busy PHP-FPM processes, it will make sure that there are always 45 child processes, with 5 available to handle any new incoming requests.
pm.max_spare_servers is the maximum number of unused PHP-FPM children that can hang around before they start getting killed off. This usually happens when your load spikes and drops down after awhile—for example, if PHP-FPM spikes to 55 child processes, but load drops and it is only using 30, there will be 25 processes sitting around doing nothing but waiting for a new web request to come in. Withpm.max_spare_servers set at 15, we’d kill off 10 of those spare children once they aren’t needed anymore.
pm.max_requestsis the number of requests that a single child process is allowed to serve before it’s killed and recycled. This is used to prevent memory leaks, since PHP’s garbage collector isn’t designed to be long-running. PHP 5.3 includes some improvements to the memory management, so this can be set fairly high. I recommend setting it to atleast 1000, but it depends on your code and which, if any, PHP extensions you are using, as some leak memory more than others.
Choosing the best hardware for your application
server
Fast CPU, Lots of memory
Expect each PHP-FPM process to use up to 20MB of memory each, figuring that ifpm.maxchildren is set at 100 you’ll need atleast 2GB of memory for your server.
Nginx, on the other hand, sips on memory—even a loaded application server will only see nginx using 60-80MB of memory total.
You’ll want the fastest CPU with the greatest number of cores you can get your hands on. Although PHP is single threaded, as long as you have more PHP-FPM processes than you have cores, you’ll be able to take full advantage of each core.
At the time of writing, the Intel Xeon SandyBridge 26xx series seem to be the best bang for your buck. They can run in a dual-cpu configuration, with up to 8-cores for each CPU. With HyperThreading, this effectively gives your application server 32 cores to take advantage of.
App Server: Horizontal Scaling with Nginx and PHP-FPM 34
Using a PHP Opcode cache
PHP is an interpreted programming language. Every time a PHP application is run, the interpreter needs to read your PHP source code, translate it into an internal opcode program, and then run those opcodes. You can think of the opcode program as an intermediate programming language that the interpreter can understand better—it’s really a virtual machine.
Translating your PHP source code to the opcodes has a non-trivial amount of overhead that can be saved by caching the output of the PHP -> opcode translation process and re-using for future executions. So how do we accomplish this? Well, we use an opcode cache extension. If you’re not already using one, you can easily see a 200-300% reduction in CPU usage. It’s an easy win! As of PHP 5.5, Zend OpCache is bundled with PHP. To enable it, just addopcache.enable=1 into yourphp.ini. Super easy. The downside is that the user-land memcache-type component that used to be part of APC is not part of Zend Opcache. If you depend on this shared cache, you can install something likeAPCu²⁰, which brings the user-land cache part of APC back to PHP 5.5. I go into more detail about Zend OpCache and heartaches it caused me while transitioning from APC in the CakePHP Cache Files Case Study.
If you’re using PHP 5.4 or below, are a couple of different opcode cache alternatives, and I’ll discuss them below.
Are there any downsides to using an opcode cache? For most setups, there are absolutely NO disadvantages. The only gotcha that you should know about is that if you’re using a PHP encoder/obfuscator, such as ionCube²¹ or Zend Guard²², you won’t be able to use an opcode cache. That being said, this limitation only impacts a small portion of people running closed- source, licensed PHP code.