Оптимизация PHP на Ubuntu: OPcache и PHP-FPM
Оптимизация PHP на сервере сводится в основном к двум вещам: OPcache, который держит скомпилированные скрипты в памяти, и менеджеру процессов PHP-FPM, который решает, сколько запросов выполняется одновременно. В пакетах Ubuntu оба включены, но с настройками для маленькой машины. Инструкция для PHP 8.3 на Ubuntu 24.04 (в 22.04 стоит PHP 8.1: в путях и именах служб пишите 8.1 вместо 8.3). Числа ниже — отправная точка, сверяйте их со своей нагрузкой. Свою версию PHP покажет команда: ls /etc/php/
Настройки OPcache для продакшена
OPcache включён по умолчанию, ему дают 128 МБ памяти и место под 10 000 скриптов. У фреймворка с зависимостями файлов легко бывает больше. Посчитайте PHP-файлы своего сайта и поставьте opcache.max_accelerated_files больше этого числа:
find /var/www/example.com -type f -name '*.php' | wc -lUbuntu читает все .ini из /etc/php/8.3/fpm/conf.d/ после php.ini, поэтому свой файл там переживёт обновления пакетов. Создайте его:
sudo nano /etc/php/8.3/fpm/conf.d/99-tuning.ini; OPcache
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0opcache.validate_timestamps=0 значит, что PHP больше не проверяет, изменились ли файлы, и это экономит обращение к диску на каждый запрос. Цена такая: после каждой выкладки PHP-FPM нужно перезапускать, иначе продолжит работать старый код. Если вы правите файлы на сервере руками или ваша выкладка не умеет перезапускать службы, оставьте opcache.validate_timestamps=1 и поставьте opcache.revalidate_freq=60: тогда PHP проверяет файлы раз в минуту.
sudo php-fpm8.3 -t
sudo systemctl restart php8.3-fpmПроверка должна ответить configuration file /etc/php/8.3/fpm/php-fpm.conf test is successful. Чтобы увидеть, насколько заполнен кэш, сделайте временную страницу с состоянием OPcache, откройте её в браузере и сразу удалите: она показывает пути ваших скриптов всем подряд.
echo '<?php print_r(opcache_get_status(false));' | sudo tee /var/www/example.com/opcache-check.phpОткройте https://example.com/opcache-check.php, посмотрите, что нужно, и удалите файл:
sudo rm /var/www/example.com/opcache-check.phpЕсли на странице [cache_full] => 1 или в [free_memory] осталось мало, мал opcache.memory_consumption. Если [num_cached_scripts] близко к opcache.max_accelerated_files, мал он. [opcache_hit_rate] после некоторого времени работы сайта должен быть около 100.
JIT в PHP 8.3
JIT компилирует часто выполняемый код в машинный. В PHP 8.3 он выключен, потому что opcache.jit_buffer_size по умолчанию 0. Он помогает коду, который сильно грузит процессор: вычислениям, обработке картинок. Обычный сайт, который в основном ждёт базу данных, выигрывает от него мало, так что замерьте до и после. Чтобы включить, добавьте две строки в тот же 99-tuning.ini и перезапустите PHP-FPM:
; JIT
opcache.jit=tracing
opcache.jit_buffer_size=64MВ состоянии OPcache в [jit] появится [enabled] => 1. Память JIT идёт сверх opcache.memory_consumption. Если загружен Xdebug, JIT не работает.
PHP-FPM: режимы pm и как посчитать pm.max_children
Каждый процесс PHP-FPM обслуживает один запрос за раз, так что pm.max_children — это сколько PHP-запросов идёт одновременно. В Ubuntu там 5, большинству сайтов мало. Но слишком много — ещё хуже: если процессы не влезают в память, сервер уходит в подкачку или начинает убивать процессы. Считайте от памяти. Сначала посмотрите, сколько памяти свободно и сколько в среднем занимает один процесс PHP под настоящей нагрузкой:
free -m
ps --no-headers -o rss -C php-fpm8.3 | awk '{ sum += $1; n++ } END { print sum / n / 1024 " MB" }'Дальше так: pm.max_children = память, которую можно отдать PHP / средний размер процесса. Оставьте память системе, базе данных и всему, что ещё есть на сервере. Например, сервер на 4 ГБ с MySQL, который занимает 1,5 ГБ: 0,5 ГБ оставляем системе, PHP отдаём 2 ГБ, при процессах по 60 МБ выходит 2048 / 60 ≈ 34. Округлите вниз, до 30. Цифра из ps учитывает общую память OPcache в каждом процессе, так что настоящий запас чуть больше.
pm = dynamic(по умолчанию в Ubuntu) — держит несколько свободных процессов наготове и добавляет новые доpm.max_children. Подходит большинству серверов.pm = static— всегда ровноpm.max_childrenпроцессов. Для нагруженного сервера, где работает только этот сайт и памяти хватает на все процессы.pm = ondemand— запускает процессы только на запросы и гасит их черезpm.process_idle_timeout(по умолчанию 10 секунд). Для маленького сервера или множества пулов с небольшим трафиком.
sudo nano /etc/php/8.3/fpm/pool.d/www.confpm = dynamic
pm.max_children = 30
pm.start_servers = 6
pm.min_spare_servers = 4
pm.max_spare_servers = 8
pm.max_requests = 500pm.start_servers обычно ставят посередине между двумя значениями spare. pm.max_requests = 500 перезапускает каждый процесс после 500 запросов, и утечки памяти в библиотеках не растут бесконечно. По умолчанию там 0, то есть процессы не перезапускаются никогда.
sudo php-fpm8.3 -t
sudo systemctl restart php8.3-fpm
sudo grep max_children /var/log/php8.3-fpm.logСтрока WARNING: [pool www] server reached pm.max_children setting (30), consider raising it значит, что запросам пришлось ждать свободный процесс. Поднимайте лимит, только если позволяет память, а иначе сначала ищите медленные запросы.
Как найти медленные запросы PHP через slowlog
PHP-FPM умеет записывать стек вызовов каждого запроса, который работает дольше заданного времени: видно скрипт, функцию и строку, где он застрял. По умолчанию это выключено. Добавьте две строки в тот же файл пула:
request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm.slow.logsudo php-fpm8.3 -t
sudo systemctl restart php8.3-fpm
sudo tail -n 50 /var/log/php8.3-fpm.slow.logКаждая запись начинается со времени, пула и script_filename, дальше идёт стек вызовов, самый глубокий вызов сверху. Начните с 5 секунд и снижайте порог, когда самые тяжёлые запросы исправлены.
Realpath cache
Готовые пути подключаемых файлов PHP держит в realpath cache, чтобы не спрашивать диск каждый раз. В нынешних версиях PHP ему дают 4 МБ, этого хватает большинству проектов, а записи живут 120 секунд. При большом числе файлов и opcache.validate_timestamps=0 срок подольше сэкономит ещё немного обращений к диску:
; Realpath cache
realpath_cache_size=4096K
realpath_cache_ttl=600После правки перезапустите PHP-FPM. Если задан open_basedir, кэш не работает вовсе. Чтобы увидеть, насколько он заполнен, сделайте так же, как с проверкой OPcache, временную страницу с <?php echo realpath_cache_size();: она покажет занятые байты. Потом удалите её.
Если вы работаете и на Mac

Сборка мусора в MacWashing находит кэш всех знакомых ей программ, в том числе спрятанный внутри их данных, а ещё журналы, мусор Xcode и остатки удалённых программ. Она показывает, сколько места занимает каждая находка, и очищает всё за один раз. Всё спорное остаётся без галочки, пока вы не решите.
Бесплатная пробная версия · macOS 14 и новее
Вопросы и ответы
Что даёт больше всего при оптимизации PHP?
OPcache, в который влезают все ваши скрипты, и pm.max_children, который помещается в память. Дальше ищите медленные запросы через slowlog: чаще всего время уходит на запросы к базе, а не на сам PHP.
Мои правки не видны на сайте. Почему?
При opcache.validate_timestamps=0 PHP выполняет код из кэша, пока PHP-FPM не перезапустят. Перезапускайте его после каждой выкладки или добавьте команду в скрипт выкладки: sudo systemctl restart php8.3-fpm
Сколько pm.max_children на сервере с 2 ГБ?
Если PHP можно отдать около 1,2 ГБ, а процесс занимает 50 МБ, то около 24. Размер процесса измерьте на своём сайте: тяжёлый фреймворк может занимать 100 МБ и больше на процесс.