Tuning simulator: mod_php, PHP-FPM or FrankenPHP
Pick the situation closest to yours: each way to serve PHP shows what its default settings give, and the settings optimized for your situation. It is a model calibrated on our benchmarks, not a load test: use it to choose and to start, then measure.
1Your situation
memory_get_peak_usage(), profiler).Application details: waiting time, heavy requests, framework boot, pages
2Settings and impact
What the numbers mean
- Max users
- With a fixed number of containers: the simultaneous users they serve together, with pages in under 1 s (or twice the response time, for slow requests) and the CPU below 90%. The white mark is your number of users.
- Containers needed
- With an automatic number of containers: how many are needed for your users, with 25% of headroom, and the users they serve at most.
- Page time
- What one of your users waits for a page: PHP, the wait for a free PHP worker, and the network round trips to open connections.
- Twice the users
- A rush of 5 minutes at twice your users (a newsletter, Monday 9 am): the page time at its end. When the containers cannot follow, the requests queue up and the time grows; above 60 s, the proxies and the browsers give up. Out of memory: the kernel kills processes.
- Memory, worst case
- Every PHP worker busy with an average request, plus one heaviest request.
- Default / Optimized / Custom
- The default settings of the v5 images, the settings optimized for your situation, and your own (“Custom”, from the optimized ones).
3What to choose
Two ways to run PHP
mod_php (apache variant) runs PHP inside each Apache process (mpm_prefork): a
process is held for the whole life of a connection, PHP or not: static files, TLS handshakes, idle keep-alive
connections. Each process costs the memory of a PHP request.
PHP-FPM (fpm variant, PHP_FPM_WEB_SERVER=apache) keeps the connections in
Apache (mpm_event), with cheap threads; a PHP worker is only busy while PHP runs, and the number
of workers is set for the CPU and the memory, not for the connections.
Behind a reverse proxy
The proxy terminates HTTPS and keeps the connections of the browsers: the container only receives short requests. Well tuned (keep-alive off, enough processes for the memory), mod_php serves about as many users as PHP-FPM, as measured in our benchmark. The difference is in the margins: the memory of the processes, and the behavior when twice the users come. Keep-alive must stay off for mod_php: the proxy parks its idle connections on the processes (p99 of 14 s measured).
HTTPS served by the container
Every connection of a browser holds a mod_php process: with keep-alive, the idle connections (up to 6 per browser) hold processes for seconds; without it, each request pays a TCP and TLS handshake, in the heavy process and in the page time of the users, more on mobile networks. mod_php cannot serve HTTP/2. PHP-FPM keeps one HTTP/2 connection per browser in Apache: +63% of pages in our benchmark with light pages, less with heavy pages where PHP dominates.
Memory and rushes
The v5 default of the apache variant allows 150 processes: with requests of 100 MB, a rush starts
them all and exhausts the memory (208 processes killed in our benchmark with 48 MB pages). PHP-FPM caps the PHP
workers independently of the connections: in a rush, the extra requests wait in a queue instead of starting
processes, and fewer processes compete for the CPU (+16% to +29% of pages served in our benchmark).
nginx instead of the built-in Apache
The fpm variant can also run without its Apache, behind your own nginx container: PHP-FPM is
configured the same way, nginx serves the static files for a bit less CPU. It is worth it with many static
files and no CDN; otherwise the built-in Apache keeps a single container.
FrankenPHP (v6)
Planned as a variant of the v6 images. Its worker mode keeps the application booted between requests: it saves the boot of the framework, a few percent of a heavy request, far from the ×3 of “hello world” benchmarks. PHP runs in threads (ZTS build), a few percent slower. WordPress does not run in worker mode. Not measured yet: estimate only.
4Configuration
Model
- CPU per page = PHP requests × CPU time (response time minus waiting) + static files × 0.3-0.4 ms + new connections × (0.1 ms, + 0.9 ms for a TLS handshake); costs measured on a 2.6 GHz core, close to a cloud vCPU.
- CPU sharing stretches the CPU time by 1 / (1 − CPU usage). A PHP request holds a worker for its waiting time plus its stretched CPU time; the wait for a free worker uses the Erlang C formula.
- mod_php processes are also held by static files and, with HTTPS, by the handshakes (2 round trips) and, with
keep-alive, by the idle connections of the browsers (up to 6, for
KeepAliveTimeout). - Memory: base + per worker 6 MB plus the request memory, plus one heaviest request. Rush: the CPU capacity reduced by the contention of the PHP processes (calibrated on the measured peaks); the requests above it queue up.
- Optimized settings: the most users within 90% of the container memory, then the best rush, then the fewest processes. In Custom, the settings you change are kept and the other ones are optimized around them.