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-порт для внешних проверок или дополнительного инстанса.

Настройка upstream для PHP-FPM в nginx - comments

En
Upstream php fpm (php)