Kingsfordweg 151, 1043GR Amsterdam, The Netherlands ● 24/7 support — support@novogara.com
Knowledge base

Tuning PHP-FPM

PHP on a dedicated server has far more room than it had on shared hosting. Three settings get you most of the way, and one log file finds what is left.

Size the pool to memory, not optimism

Each PHP-FPM worker holds memory for the life of a request. Measure what yours actually use:

ps -ylC php-fpm8.4 --sort:rss | awk '{sum+=$8; n++} END {print sum/n/1024 " MB average"}'

Then divide the memory you are willing to give PHP by that figure, and set pm.max_children to the result. Setting it higher does not serve more traffic; it swaps, and a swapping machine is slower than a queue. See swap and the OOM killer.

Which process manager

pm = static
A fixed number of workers, always running. The fastest and the most predictable. Right for a dedicated machine running one busy application.
pm = dynamic
Scales between a minimum and a maximum. Sensible when several sites share the machine and their peaks do not coincide.
pm = ondemand
Starts workers only when needed. Good for many idle sites, poor for anything latency-sensitive — the first request pays for the start-up.

Give each site its own pool with its own user. One site then cannot read another's files or exhaust another's workers.

Opcache: the single biggest win

Without it, PHP compiles your code on every request. With it, the compiled form sits in shared memory. In php.ini:

opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=50000
opcache.validate_timestamps=0

That last one stops PHP checking whether files changed — a real gain, and it means you must reload PHP-FPM as part of every deploy or your new code will not be used. On a development machine, leave it at 1.

Raise realpath_cache_size to 4M as well; frameworks with deep autoloaders spend a surprising amount of time resolving paths.

Find what is actually slow

The slow log is the most useful and least used feature in PHP-FPM. In your pool:

slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 5s

Any request over five seconds is written out with a full stack trace, so you see the exact function that was blocking. Nine times out of ten it is a database query or an external HTTP call with no timeout.

Checks worth doing once

  • memory_limit per pool rather than globally, so one runaway script cannot take the machine.
  • max_execution_time set to something honest. Infinite is not a value.
  • Sessions on tmpfs or Redis rather than a spinning disk.
  • expose_php = Off, and no display_errors in production.
  • Watch the pool status page for listen queue rising — that is the signal you are out of workers, long before users complain.

Still stuck? Mail support@novogara.com — an engineer answers, at any hour. Back to the knowledge base