Upstream-серверы PHP-FPM: настройка веб-сервера
Основы upstream для PHP-FPM
Основное решение: простой upstream с одним сервером
Для обработки PHP-скриптов через PHP-FPM в nginx используется директива fastcgi_pass, которая указывает адрес upstream-сервера. Наиболее распространённый способ - определить блок upstream с одним сервером, а затем ссылаться на него в location. Это делает конфигурацию более гибкой и удобной для последующих изменений.
upstream php_backend {
server unix:/var/run/php/php8.2-fpm.sock;
}
server {
listen 80;
server_name example.com;
root /var/www/html;
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass php_backend;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Mod rewrite index php (настройка mod_rewrite для index.php)
В данном примере используется Unix-сокет /var/run/php/php8.2-fpm.sock. Если PHP-FPM слушает TCP-порт, можно указать server 127.0.0.1:9000;. Директива include fastcgi_params подключает стандартные параметры FastCGI.
Типичные ошибки и их решение:
- 502 Bad Gateway - PHP-FPM не запущен или не слушает указанный сокет/порт. Проверьте статус службы:
systemctl status php8.2-fpm. - Permission denied при использовании Unix-сокета - пользователь nginx (обычно www-data) не имеет прав на чтение/запись сокета. Добавьте пользователя в группу, владеющую сокетом, или измените права в конфигурации PHP-FPM (
listen.owner,listen.group). - 504 Gateway Time-out - скрипты выполняются дольше, чем установленный таймаут в nginx (
fastcgi_read_timeout). Увеличьте значение.
Цель: обеспечить базовую обработку PHP-запросов. Подходит для большинства сайтов с одним сервером бэкенда.
Как распределять нагрузку между несколькими серверами PHP-FPM?
Если требуется масштабирование или отказоустойчивость, можно указать несколько серверов в блоке upstream. По умолчанию nginx использует алгоритм round-robin. Поддерживаются также least_conn, ip_hash и random.
upstream php_backend {
server 10.0.0.1:9000 weight=3;
server 10.0.0.2:9000;
server 10.0.0.3:9000 backup;
}
server {
...
location ~ \.php$ {
fastcgi_pass php_backend;
...
}
}
Upstream php fpm (настройка upstream для php-fpm в nginx)
Параметр weight задаёт вес сервера (по умолчанию 1). backup делает сервер резервным - он используется только когда основные серверы недоступны. Для сессионной привязки (sticky sessions) применяется ip_hash:
upstream php_backend {
ip_hash;
server 10.0.0.1:9000;
server 10.0.0.2:9000;
}
запустить локальный сервер php (запуск локального сервера php)
Возможные проблемы:
- Неравномерная нагрузка при round-robin, если серверы разной производительности - используйте weight.
- Потеря сессий при смене бэкенда - если сессии хранятся локально, применяйте ip_hash или внешнее хранилище (Redis, Memcached).
- Резервный сервер не перехватывает трафик - проверьте, что основные серверы действительно помечены как недоступные (max_fails, fail_timeout).
Когда использовать: при росте нагрузки, необходимости горизонтального масштабирования или отказоустойчивости.
Как повысить производительность с помощью Unix-сокета?
Unix-сокеты быстрее TCP, так как не требуют сетевого стека. Они подходят, когда nginx и PHP-FPM работают на одном сервере. Убедитесь, что PHP-FPM настроен на прослушивание сокета (например, listen = /run/php/php8.2-fpm.sock). В nginx достаточно указать путь к сокету.
upstream php_backend {
server unix:/var/run/php/php8.2-fpm.sock;
}
Php non thread safe (php non-thread-safe (не потокобезопасная версия))
Частые ошибки:
- 404 Not Found при использовании сокета - часто возникает из-за неправильного пути. Проверьте фактический путь к сокету через
php-fpm -i | grep listen. - Отказ в соединении - сокет не создан или PHP-FPM не запущен.
Случаи использования: выделенные серверы с одним PHP-FPM, минимальная задержка, отсутствие сетевой изоляции.
Как уменьшить накладные расходы на соединения с помощью keepalive?
По умолчанию nginx открывает новое соединение для каждого запроса к PHP-FPM. Директива keepalive в блоке upstream позволяет переиспользовать соединения, снижая задержки и нагрузку на CPU. Для включения необходимо добавить keepalive и в location указать fastcgi_keep_conn on;.
upstream php_backend {
server unix:/var/run/php/php8.2-fpm.sock;
keepalive 32;
}
server {
location ~ \.php$ {
fastcgi_pass php_backend;
fastcgi_keep_conn on;
...
}
}
Rewritecond request filename php (правило rewritecond для php в .htaccess)
Число 32 - максимальное количество idle-соединений, хранящихся в пуле. Настройка особенно эффективна при высокой частоте запросов.
Проблемы:
- Утечка соединений - если PHP-FPM не поддерживает keepalive (старые версии), соединения могут зависать. Используйте актуальные версии PHP (7.4+).
- Снижение производительности при слишком большом пуле - установите значение, равное примерно количеству воркеров PHP-FPM.
Когда применять: высоконагруженные сайты, где каждый миллисекунд задержки имеет значение.
Как настроить таймауты для долгих PHP-скриптов?
Таймауты контролируются директивами fastcgi_connect_timeout, fastcgi_send_timeout, fastcgi_read_timeout. По умолчанию значения обычно 60 секунд. Если скрипты выполняются дольше (например, генерация отчётов), увеличьте таймауты в контексте location.
location ~ \.php$ {
fastcgi_pass php_backend;
fastcgi_connect_timeout 30s;
fastcgi_send_timeout 120s;
fastcgi_read_timeout 300s;
...
}
Apache 2.4 php 8.2 (настройка apache 2.4 с php 8.2)
Значение fastcgi_read_timeout должно быть больше максимального времени выполнения скрипта, заданного в max_execution_time PHP.
Ошибки:
- 504 Gateway Time-out - слишком маленький
fastcgi_read_timeout. Проверьте логи nginx (/var/log/nginx/error.log). - 502 Bad Gateway - PHP-FPM завершает соединение, если скрипт превышает свой внутренний таймаут. Увеличьте
request_terminate_timeoutв пуле PHP-FPM.
Цель: корректная обработка длительных операций (импорт данных, видео-конвертация).
Как автоматически исключать неработающие бэкенды?
Nginx предоставляет пассивные проверки здоровья через параметры max_fails и fail_timeout. Если сервер не отвечает max_fails раз в течение fail_timeout, он помечается как недоступный на время fail_timeout.
upstream php_backend {
server 10.0.0.1:9000 max_fails=3 fail_timeout=30s;
server 10.0.0.2:9000 max_fails=3 fail_timeout=30s;
}
Php index сервер (настройка индексного файла php на сервере)
По умолчанию max_fails=1, fail_timeout=10s. Если все серверы недоступны, nginx возвращает 502.
Ограничения:
- Пассивная проверка срабатывает только после неудачного запроса. Для активного мониторинга требуется Nginx Plus или сторонний модуль.
- При большой нагрузке можно часто задевать fail_timeout - настройте адекватные значения.
Когда нужно: для автоматического переключения на резервные серверы без вмешательства администратора.
Как динамически выбирать бэкенд (например, по URI)?
С помощью директивы map можно привязать выбор upstream к переменной, например, части URI. Это полезно для разделения трафика (старые и новые версии PHP, A/B тестирование).
map $uri $backend {
~^/legacy php_legacy;
default php_main;
}
upstream php_main {
server unix:/run/php/php8.2-fpm.sock;
}
upstream php_legacy {
server unix:/run/php/php7.4-fpm.sock;
}
server {
location ~ \.php$ {
fastcgi_pass $backend;
...
}
}
Сложности:
- Переменная
$backendдолжна содержать имя upstream блока или строку видаunix:/path. Если значение некорректно, nginx вернёт ошибку. - При использовании map убедитесь, что переменная не пустая (иначе 502).
Случаи использования: миграция на новую версию PHP, разделение окружений (dev/staging), канареечные релизы.
Расширенные примеры конфигурации upstream
Пример 1. Балансировка с зонами shared memory и резервным сервером
upstream php_backend {
zone php_backend_zone 64k;
server 192.168.1.10:9000 weight=5;
server 192.168.1.11:9000 weight=3;
server 192.168.1.12:9000 backup;
keepalive 16;
}
server {
location ~ \.php$ {
fastcgi_pass php_backend;
fastcgi_keep_conn on;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Директива zone создаёт разделяемую память для состояния всех воркеров nginx, что позволяет синхронизировать информацию о недоступности серверов. Резервный сервер (backup) будет получать трафик только когда основные серверы помечены как отказавшие.
Пример 2. Маршрутизация на основе cookie (sticky sessions без модуля)
map $cookie_php_backend $upstream_backend {
"backend1" "10.0.0.1:9000";
"backend2" "10.0.0.2:9000";
default "10.0.0.1:9000";
}
upstream php_fallback {
server 10.0.0.1:9000;
server 10.0.0.2:9000;
}
server {
location ~ \.php$ {
if ($upstream_backend) {
fastcgi_pass $upstream_backend;
}
fastcgi_pass php_fallback;
}
}
Здесь cookie php_backend определяет целевой сервер. Если переменная задана, nginx использует её значение, иначе - fallback-блок с round-robin.
Пример 3. Логирование времени ответа upstream (upstream_response_time)
http {
log_format upstream_log '$remote_addr - $upstream_addr [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upstream_response_time: $upstream_response_time';
access_log /var/log/nginx/access.log upstream_log;
upstream php_backend {
server unix:/run/php/php8.2-fpm.sock;
}
}
Пример записи в логе: 192.168.1.100 - 127.0.0.1:9000 [10/Oct/2024:12:34:56 +0000] "GET /index.php HTTP/1.1" 200 2345 "-" "Mozilla/5.0" upstream_response_time: 0.032
Переменная $upstream_response_time содержит время обработки запроса бэкендом. $upstream_addr показывает, какой сервер обработал запрос. Это помогает в мониторинге производительности.
Пример 4. Использование fastcgi_next_upstream для перезапросов
upstream php_backend {
server 10.0.0.1:9000 max_fails=2 fail_timeout=10s;
server 10.0.0.2:9000 max_fails=2 fail_timeout=10s;
}
location ~ \.php$ {
fastcgi_pass php_backend;
fastcgi_next_upstream error timeout invalid_header http_500;
fastcgi_next_upstream_tries 3;
...
}
Если первый сервер вернёт ошибку (500), таймаут или некорректный заголовок, nginx автоматически переадресует запрос следующему серверу в upstream. fastcgi_next_upstream_tries ограничивает количество попыток.
Пример 5. Ограничение количества соединений к одному бэкенду (max_conns)
upstream php_backend {
zone php_backend_zone 64k;
server 10.0.0.1:9000 max_conns=10;
server 10.0.0.2:9000 max_conns=20;
queue 100 timeout=30s;
}
server {
location ~ \.php$ {
fastcgi_pass php_backend;
}
}
Параметр max_conns ограничивает число одновременных соединений к серверу. queue создаёт очередь ожидания для запросов, когда все соединения заняты. Это полезно, чтобы не перегружать слабые бэкенды.
Пример 6. Совместное использование TCP и Unix-сокетов в одном upstream
upstream mixed_backend {
server unix:/run/php/php8.2-fpm.sock weight=2;
server 127.0.0.1:9001 weight=1;
keepalive 8;
}
Такой подход позволяет одновременно использовать быстрый сокет для локального трафика и TCP-порт для внешних проверок или дополнительного инстанса.