Администрирование PHP через FastCGI Process Manager

Раздел: Администрирование PHP -> Веб-сервер

Основное решение: PHP-FPM с веб-сервером Nginx

PHP-FPM (FastCGI Process Manager) является альтернативой старому mod_php для Apache и предназначен для обработки PHP-скриптов через протокол FastCGI. Основное преимущество - гибкое управление пулами рабочих процессов, что позволяет эффективно распределять ресурсы сервера. Наиболее распространённая связка - Nginx в качестве фронтенд-сервера и PHP-FPM как бэкенд.

Установка и базовая настройка

На сервере с Ubuntu или Debian установка выполняется командами:

sudo apt update
sudo apt install php-fpm

После установки служба PHP-FPM запускается автоматически. Конфигурационные файлы находятся в директории /etc/php/{version}/fpm/. Основной файл - pool.d/www.conf (или пул по умолчанию).

Типичная конфигурация пула для одного сайта:

[www]
user = www-data
group = www-data
listen = /run/php/php8.1-fpm.sock
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 500

Настройка Nginx для передачи запросов в PHP-FPM:

server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    index index.php index.html;

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    }
}

После изменения конфигурации требуется перезагрузка обеих служб:

sudo systemctl restart php8.1-fpm nginx

Типичная проблема: 502 Bad Gateway

Ошибка 502 возникает, когда Nginx не может соединиться с PHP-FPM. Причины: неверный путь к сокету, не запущена служба, неправильные права доступа к сокету. Проверка статуса:

sudo systemctl status php8.1-fpm

Если сокет не найден, проверьте файл /run/php/ (должен присутствовать .sock). Иногда требуется изменить listen.owner и listen.group в пуле на www-data.

Для повышения производительности рекомендуется использовать pm = static, если сервер выделен под PHP и количество запросов стабильно. Параметр pm.max_children устанавливается из расчёта доступной памяти: (Total RAM - RAM для ОС и других служб) / среднее потребление PHP-процесса.


Как настроить несколько пулов PHP-FPM для разных сайтов?

Каждый сайт может иметь собственный пул с индивидуальными параметрами. Это изолирует процессы и предотвращает влияние одного сайта на другой. Создайте файл конфигурации для второго пула, например /etc/php/8.1/fpm/pool.d/site2.conf:

[site2]
user = site2user
group = site2user
listen = /run/php/site2.sock
pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 10s
pm.max_requests = 200
chdir = /var/www/site2

В Nginx для каждого виртуального хоста указывается свой сокет:

server {
    listen 80;
    server_name site2.example.com;
    root /var/www/site2;
    location ~ \.php$ {
        fastcgi_pass unix:/run/php/site2.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

Проблема: ошибка доступа к сокету. Убедитесь, что пользователь Nginx (www-data) имеет права на чтение/запись сокета. Добавьте в пул параметр listen.mode = 0666 или измените группу.

Какие режимы управления процессами существуют и когда их применять?

PHP-FPM поддерживает три режима (pm):

  • dynamic - количество процессов изменяется в зависимости от нагрузки. Подходит для серверов с переменной нагрузкой.
  • static - фиксированное число процессов (pm.max_children). Рекомендуется для выделенных серверов с предсказуемой нагрузкой.
  • ondemand - процессы создаются только при поступлении запроса и завершаются после простоя. Экономит память, но увеличивает задержку первого запроса. Подходит для сайтов с редкими посещениями.

Пример конфигурации для режима ondemand с таймаутом простоя:

[ondemand_pool]
pm = ondemand
pm.max_children = 20
pm.process_idle_timeout = 15s
pm.max_requests = 1000

Как отследить активность процессов PHP-FPM?

Встроенный статус-страница. Включите в пуле:

pm.status_path = /status

В Nginx добавьте location для доступа:

location /status {
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

Откройте в браузере http://example.com/status. Вы увидите JSON или HTML с информацией о пуле: количество активных, idle, total процессов.

Ошибка: статусная страница возвращает 404

Проверьте, что pm.status_path задан в пуле, а Nginx передаёт запрос на PHP-FPM (а не ищет файл). Убедитесь, что в fastcgi-params есть SCRIPT_FILENAME.

Как ограничить время выполнения скриптов и объём памяти?

Параметры PHP для пула задаются через php_admin_value:

[www]
php_admin_value[memory_limit] = 128M
php_admin_value[max_execution_time] = 30
php_admin_value[max_input_time] = 60
php_admin_value[upload_max_filesize] = 10M
php_admin_value[post_max_size] = 12M

Значения переопределяют глобальные настройки php.ini для конкретного пула. Это позволяет разным сайтам иметь разные лимиты.

Проблема: PHP-скрипты не могут превысить заданные лимиты

Если скрипту требуется больше времени или памяти, увеличьте соответствующие значения в пуле или временно через ini_set, если пул разрешает переопределение (но это не рекомендуется из-за безопасности).

Как обеспечить логирование ошибок PHP-FPM?

Основной лог PHP-FPM (ошибки пула и общие) хранится в /var/log/php8.1-fpm.log. Для каждого пула можно задать отдельный лог через php_admin_value[error_log]. Также можно перенаправить stderr в syslog:

[www]
catch_workers_output = yes
php_admin_value[error_log] = /var/log/php/www-error.log
php_admin_flag[log_errors] = on

Просмотр логов в реальном времени:

sudo tail -f /var/log/php8.1-fpm.log

Проблема: логи не пишутся. Проверьте права на директорию /var/log/php/ и наличие файла. Убедитесь, что catch_workers_output включён.

Расширенные примеры настройки и диагностики PHP-FPM

1. Динамическое изменение пула без перезагрузки (reload)

При изменении конфигурации пула можно выполнить graceful reload без потери активных соединений:

Пример
sudo systemctl reload php8.1-fpm

Результат: служба перечитывает файлы конфигурации, текущие процессы завершают обработку запросов, новые процессы запускаются с новыми параметрами. Эквивалентная команда для init.d:

Пример
sudo /etc/init.d/php8.1-fpm reload

2. Настройка PHP-FPM с использованием TCP-сокета вместо Unix-сокета

Иногда требуется делить пул между несколькими серверами. Используйте TCP-сокет:

Пример
[www]
listen = 127.0.0.1:9000
listen.allowed_clients = 127.0.0.1

В Nginx:

Пример
fastcgi_pass 127.0.0.1:9000;

Результат: запросы проходят через TCP loopback. Unix-сокет быстрее, TCP удобнее для сетевого разделения.

3. Ограничение количества одновременных запросов на воркер

Параметр pm.max_requests определяет, сколько запросов обработает один процесс до его перезапуска. Это помогает бороться с утечками памяти:

Пример
[www]
pm.max_requests = 1000

Мониторинг используйте команду:

Пример
ps aux | grep php-fpm

Вы увидите, что каждый процесс завершает работу после 1000 запросов и заменяется новым.

4. Использование пула с разными версиями PHP

На одном сервере можно установить несколько версий PHP (например, 7.4 и 8.1) и запустить для каждого свой PHP-FPM с разными пулами. В Nginx для разных location укажите соответствующий сокет:

Пример
location ~ \.php$ {
    # Основная версия PHP 8.1
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}

location /legacy/ {
    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
    }
}

Такой подход используется при миграции проектов на новую версию PHP.

5. Профилирование медленных запросов

Включите логирование медленных запросов (slow log) для выявления узких мест в PHP-скриптах:

Пример
[www]
request_slowlog_timeout = 5s
slowlog = /var/log/php/slow.log

Пример записи в slow.log:

[12-Jun-2025 10:15:22] [pool www] pid 12345
script_filename = /var/www/html/index.php
[0x00007f1234567890] sleep() /var/www/html/index.php:10
[0x00007f1234567891] main() /var/www/html/index.php:1

Результат показывает, что скрипт выполнялся дольше 5 секунд из-за вызова sleep() в строке 10.

6. Мониторинг пула через Unix-сокет с помощью socat

Используйте утилиту socat для отправки команды статуса в PHP-FPM:

Пример
echo "GET /status" | sudo socat - UNIX-CONNECT:/run/php/php8.1-fpm.sock

Результат (фрагмент):

pool:                 www
process manager:      dynamic
start time:           12/Jun/2025:10:00:00 +0000
start since:          915
accepted conn:        1024
listen queue:         0
max listen queue:     0
listen queue len:     128
idle processes:       2
active processes:     1
total processes:      3
max active processes: 5
max children reached: 0
slow requests:        0

7. Настройка пула для использования переменных окружения

Передавайте переменные окружения из веб-сервера в PHP с помощью директивы env:

Пример
[www]
env[APP_ENV] = production
env[DB_HOST] = localhost
env[DB_NAME] = myapp

В коде PHP доступ через getenv('APP_ENV'). Полезно для разделения конфигураций разработки и продакшна.

8. Решение проблемы “Unable to create child process”

Ошибка возникает при нехватке памяти или превышении лимита пользовательских процессов. Проверьте системные лимиты:

Пример
ulimit -u   # лимит пользовательских процессов
cat /proc/sys/kernel/pid_max

Увеличьте лимит в /etc/security/limits.conf:

Пример
www-data soft nproc 1024
www-data hard nproc 2048

После изменения перезапустите PHP-FPM. Если проблема сохраняется, уменьшите pm.max_children.

9. Использование pm.status_path для автоматического мониторинга (Prometheus)

Создайте exporter для сбора метрик. Включите статусную страницу в формате JSON:

Пример
pm.status_path = /status?json

Пример ответа:

{"pool":"www","process-manager":"dynamic","start-time":1718183200,"start-since":200,"accepted-conn":500,"listen-queue":0,"max-listen-queue":0,"listen-queue-len":128,"idle-processes":2,"active-processes":1,"total-processes":3,"max-active-processes":4,"max-children-reached":0,"slow-requests":0}

С помощью скрипта (Python, bash) парсите JSON и передайте в Prometheus или любой другой мониторинг.

10. Изоляция пулов по сети namespace

Для усиления безопасности можно запустить каждый пул в отдельном namespace с помощью systemd. Создайте сервисный файл для каждого пула (например, /etc/systemd/system/php8.1-fpm@.service):

Пример
[Unit]
Description=PHP-FPM for %i
After=network.target

[Service]
Type=notify
ExecStart=/usr/sbin/php-fpm8.1 --nodaemonize --fpm-config /etc/php/8.1/fpm/pool.d/%i.conf
PrivateTmp=true
ProtectSystem=full

Затем запустите экземпляр для пула site2:

Пример
sudo systemctl start php8.1-fpm@site2

Этот метод используется в контейнеризированных средах для максимальной изоляции.

PHP-FPM (FastCGI Process Manager) - comments

En
Php fpm (php)