ZIM-НИЙ SAAALEЗимние скидки: до −50% на старт и −20% на продление
до 31.01.2026 Подробнее
Выберите продукт

PHP-FPM, systemd socket activation и zero downtime reload без 502

Типичная боль продакшн‑проектов на Nginx + PHP-FPM: правим конфиг, делаем systemctl reload php-fpm — и в мониторинге всплеск 502, пользователи видят ошибки, растут алерты. Разберём, как с помощью systemd socket activation и staged reload обновлять конфигурацию и версии PHP без простоя и с предсказуемым поведением под нагрузкой.
PHP-FPM, systemd socket activation и zero downtime reload без 502

Почти у каждого, кто держит продакшн на Nginx + PHP-FPM, хотя бы раз случалось: аккуратно правим конфиг, делаем systemctl reload php-fpm — и внезапно в мониторинге всплеск 502, пользователи жалуются, а в логах connect() to unix:/run/php-fpm.sock failed. Особенно критично это ощущается под нагрузкой.

Хорошая новость: с современным systemd и правильно настроенным PHP-FPM можно добиться практического zero downtime reload — когда перезапуск или обновление PHP вообще не приводит к 502, а входящие соединения «подвешиваются» на сокете, пока новый процесс пула не поднимется.

В этой статье разберём, как работает связка PHP-FPM + systemd socket activation, почему «голый» reload даёт 502 и как построить staged-перезагрузку (поэтапный reload/restart) без простоев, в том числе при смене конфигурации или версии PHP.

Откуда берутся 502 при reload PHP-FPM

Начнём с того, что происходит при типичном reload службы PHP-FPM, если у вас на сервере:

  • Nginx слушает порт 80/443;
  • PHP-FPM слушает Unix-сокет, например /run/php/php-fpm.sock;
  • Nginx проксирует PHP-запросы через fastcgi_pass unix:/run/php/php-fpm.sock;.

При systemctl reload php-fpm (или аналоге для конкретной версии, типа php8.2-fpm) происходит примерно следующее:

  • master-процесс PHP-FPM перечитывает конфигурацию;
  • он может на короткое время закрыть слушающий сокет, чтобы его перевесить (например, если путь или разрешения изменились);
  • рабочие процессы перезапускаются или ротацируются;
  • в этот момент Nginx при новом запросе пытается открыть соединение к сокету и иногда получает ECONNREFUSED или ENOENT;
  • в результате клиент видит 502 Bad Gateway.

При небольшом трафике этого можно и не заметить, но на нагруженных проектах даже доли секунды простоя превращаются в десятки–сотни 502-ошибок.

Идея socket activation в том, чтобы вынести слушающий сокет из сферы ответственности PHP-FPM в systemd, чтобы служба могла перезапускаться отдельно от сокета, а Nginx всегда видел живой endpoint.

Как работает systemd socket activation

systemd умеет поднимать сокеты как отдельные .socket-юниты и передавать дескрипторы слушающих сокетов в сервисы при их старте. Это классическая socket activation:

  • первым стартует php-fpm.socket — systemd создаёт Unix- или TCP-сокет и начинает принимать подключения;
  • когда приходит первое соединение, systemd автоматически стартует php-fpm.service и передаёт ему уже открытый сокет;
  • последующие рестарты сервиса не требуют пересоздания сокета: дескриптор может оставаться прежним или переоткрываться без окна времени, в которое сокет недоступен наружу;
  • клиент (Nginx) продолжает писать в сокет, а systemd и ядро позаботятся о том, чтобы запросы не терялись, а ждали, пока сервис снова поднимется (в разумных пределах).

Если мы разделим php-fpm.service и php-fpm.socket, то сможем перезапускать service практически без 502: сокет продолжает принимать подключения, очередь соединений висит, а как только новый master PHP-FPM встанет на ноги, он принимает уже накопившиеся запросы.

Такую схему особенно удобно применять на серверах с высокой нагрузкой или при обслуживании большого числа сайтов, как на классическом виртуальном хостинге, так и на собственном VDS.

Диаграмма потока запросов от Nginx к systemd-сокету и сервису PHP-FPM с очередью подключений

Проверяем текущую конфигурацию PHP-FPM и systemd

Для начала нужно понять, как именно PHP-FPM интегрирован с systemd в вашей системе. На Debian/Ubuntu это чаще всего юниты типа php8.2-fpm.service, на RHEL/AlmaLinux — php-fpm.service.

Посмотрим список юнитов:

systemctl list-unit-files | grep php-fpm
systemctl list-unit-files | grep php8.2-fpm

Если вы видите только *.service без *.socket, значит socket activation не используется. Нам нужно будет создать соответствующие юниты руками или адаптировать имеющиеся.

Также стоит посмотреть, как PHP-FPM сейчас слушает сокет. Откройте конфиг пула, например:

grep -n "^listen" /etc/php/8.2/fpm/pool.d/www.conf

Типичные варианты:

  • listen = /run/php/php8.2-fpm.sock — Unix-сокет;
  • listen = 127.0.0.1:9000 — TCP-порт;
  • listen = /run/php-fpm/www.sock — свой путь.

Для socket activation мы должны убедиться, что:

  • PHP-FPM настроен слушать сокет, который будет создавать systemd;
  • у юнита .service корректно настроено взаимодействие с .socket-юнитом.

Проектируем связку: Nginx → systemd socket → PHP-FPM

Целевая схема работы:

  • Nginx по-прежнему использует fastcgi_pass unix:/run/php/php-fpm.sock; (или любой выбранный путь);
  • php-fpm.socket создаётся и принадлежит systemd;
  • php-fpm.service стартует по demand или всегда включён, но не управляет жизненным циклом сокета;
  • reload или restart php-fpm.service не разрушает сокет;
  • в момент рестарта PHP-FPM новые соединения продолжают устанавливаться к сокету, а ядро держит их в listen-очереди до тех пор, пока сервис снова не начнёт принимать.

Это уже даёт минимальные 502, но чтобы довести схему до «почти нуля» даже под большой нагрузкой, нужно аккуратно подобрать очереди, лимиты и staged-подход к reload.

Создаём systemd socket для PHP-FPM

Рассмотрим универсальный пример для Unix-сокета /run/php/php-fpm.sock. Пусть у нас есть unit /etc/systemd/system/php-fpm.service (или override к дистрибутивному юниту), а мы хотим добавить php-fpm.socket.

Создаём файл /etc/systemd/system/php-fpm.socket:

[Unit]
Description=PHP-FPM Socket
PartOf=php-fpm.service

[Socket]
ListenStream=/run/php/php-fpm.sock
SocketUser=nginx
SocketGroup=nginx
SocketMode=0660
Backlog=512

[Install]
WantedBy=sockets.target

Комментарий по полям:

  • ListenStream — путь к Unix-сокету, который будет использовать Nginx;
  • SocketUser, SocketGroup, SocketMode — владелец и права на сокет (владелец или группа PHP-FPM и Nginx должны иметь право доступа);
  • Backlog — глубина очереди запросов на сокете; играет важную роль при пиках нагрузки и в момент перезапуска сервиса.

Далее нужно убедиться, что сам php-fpm.service не пытается самостоятельно создавать сокет. Обычно это настраивается в www.conf или другом пуле: там должен быть тот же listen, но уже без забот о правах сокета — ими ведает systemd.

Пример фрагмента /etc/php/8.2/fpm/pool.d/www.conf:

listen = /run/php/php-fpm.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660

При использовании socket activation эти параметры прав на сокет можно опустить или оставить «на всякий случай», но критично, чтобы путь совпадал с ListenStream в php-fpm.socket.

Настройка php-fpm.service для работы с socket activation

Далее важно настроить сам сервисный юнит. Посмотрим текущий юнит:

systemctl cat php-fpm.service

Нас интересуют:

  • ExecStart — как стартует PHP-FPM;
  • Type — желательно simple или notify;
  • Sockets — привязка к php-fpm.socket, если уже есть.

Если у вас дистрибутивный юнит и вы не хотите его менять, создайте override:

systemctl edit php-fpm.service

И добавьте, например:

[Unit]
Requires=php-fpm.socket
After=php-fpm.socket

[Service]
Type=notify
NotifyAccess=main
Sockets=php-fpm.socket

Режим Type=notify удобен тем, что systemd будет считать сервис запущенным только после того, как php-fpm отправит уведомление через sd_notify, а Nginx успеет корректно законнектиться.

Если ваша сборка PHP-FPM не умеет sd_notify, можно оставить Type=simple. Тогда «окно» между стартом и фактической готовностью будет чуть менее контролируемым, но в большинстве сценариев этого достаточно.

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

systemctl daemon-reload

Миграция на socket activation без простоя

Переход на socket activation тоже желательно выполнить без 502. Безопасный алгоритм миграции может быть таким:

  1. Убедиться, что Nginx использует тот же fastcgi_pass, что и новый сокет (обычно ничего менять не нужно).
  2. Создать файл php-fpm.socket и override для php-fpm.service.
  3. Перезапустить только Nginx с тестом конфига, чтобы исключить опечатки.
nginx -t
systemctl reload nginx
  1. Аккуратно перевести PHP-FPM на управление через socket:
systemctl stop php-fpm.service
rm -f /run/php/php-fpm.sock
systemctl start php-fpm.socket
systemctl start php-fpm.service

На этом этапе Nginx будет подключаться к сокету, созданному systemd. Дальнейшие restart и reload php-fpm.service уже не должны разрушать сокет.

Если вы планируете в будущем мигрировать сайты на другой сервер без простоя, посмотрите также материал поэтапная миграция сайтов с почти нулевым временем простоя.

Staged reload: поэтапный перезапуск PHP-FPM без 502

Socket activation — фундамент, но для действительно мягкого перезапуска под нагрузкой полезен staged-подход: мы не просто делаем один restart, а разделяем операцию на шаги, чтобы минимизировать риск отказов.

Пример логики staged reload для PHP-FPM под systemd можно описать так:

  1. Сначала проверить конфиг на валидность.
  2. Перевести Nginx (или балансировщик) в режим снижения давления на backend, если есть такая механика.
  3. Сделать мягкий reload PHP-FPM, не трогая сокет.
  4. При необходимости выполнить restart, контролируя очереди.

Базовый safe reload скриптом может выглядеть так (упрощённый пример):

#!/bin/sh
set -e

# Проверяем конфиг PHP-FPM
php-fpm -t

# Проверяем конфиг Nginx
nginx -t

# Мягкий reload PHP-FPM
systemctl reload php-fpm.service

# Опционально можно подождать и проверить статус
sleep 2
systemctl is-active php-fpm.service

Благодаря socket activation даже если reload занял чуть дольше, чем обычно, Nginx по-прежнему пишет в живой сокет. Короткие всплески 502 теперь возможны только при фатальной ошибке конфигурации (когда PHP-FPM вообще не стартует).

Zero downtime reload при смене версии PHP

Интереснее становится, когда речь не о правке пула, а об обновлении версии PHP. Например, вы держите одновременно php8.1-fpm и php8.3-fpm и хотите переключиться без простоя. Здесь socket activation тоже помогает: общий сокет может принадлежать systemd, а разные версии PHP-FPM по очереди привязываются к нему.

Один из рабочих паттернов:

  • Сокет фиксированный: /run/php/app.sock, описан в app-php.socket;
  • два сервиса: php81-fpm.service и php83-fpm.service, оба умеют принимать дескриптор от app-php.socket;
  • в каждый момент времени активен только один из них.

Staged-переключение выглядит так:

  1. Развернуть и настроить php83-fpm, протестировать его на отдельном staging-сокете.
  2. Отключить php81-fpm от общего сокета (или остановить службу).
systemctl stop php81-fpm.service
  1. Мгновенно стартовать php83-fpm.service, который принимает тот же сокет.
systemctl start php83-fpm.service

С точки зрения Nginx сокет не менялся, подключения на уровне ядра будут в очереди на одном и том же дескрипторе, и при быстром переключении окно отсутствия обработчика запросов минимально (часто незаметно на практике).

Нюанс: необходимо убедиться, что сокет не закрывается при остановке php81-fpm.service. Для этого именно systemd должен быть его владельцем, а не PHP-FPM.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Тонкая настройка очередей и лимитов для сокета

При активном трафике важна не только логика reload, но и глубина listen-очереди. В связке Nginx → socket → PHP-FPM действует несколько лимитов:

  • Backlog в php-fpm.socket;
  • системный net.core.somaxconn (даже для Unix-сокетов влияет общий потолок);
  • лимиты на количество процессов или child-процессов PHP-FPM (pm.max_children и др.);
  • таймауты Nginx (fastcgi_connect_timeout, fastcgi_read_timeout и т.п.).

Если reload или restart занимают, например, 200–500 мс, а входящий RPS высокий, то сокет должен уметь буферизовать десятки–сотни соединений, иначе часть клиентов получит ошибки на этапе установления соединения.

Рекомендации:

  • Backlog в .socket стоит ставить как минимум в диапазоне 256–512, а на нагруженных системах — и выше, с учётом somaxconn;
  • fastcgi_connect_timeout в Nginx нельзя опускать до экстремально низких значений (0.1–0.3 s), иначе при reload соединения будут слишком легко отваливаться;
  • анализируйте логи Nginx на предмет upstream timed out (110: Connection timed out) while connecting to upstream — это маркер того, что сокет или backend не успевает.

Разбор типичных ошибок при переходе на socket activation

1. Сокет создаётся и systemd, и PHP-FPM

Симптомы:

  • в логах PHP-FPM сообщение: «Unable to bind listening socket for address /run/php/php-fpm.sock: Address already in use»;
  • сервис не стартует после перевода на socket activation.

Решение: убедиться, что php-fpm.service не запускается с параметрами, которые создают свой слушающий сокет вне systemd, а пулами управляется через listen = /run/php/php-fpm.sock. В некоторых дистрибутивах сокеты переключаются через опции php-fpm.conf, убедитесь, что не осталось старых значений listen = 127.0.0.1:9000.

2. Nginx не имеет прав читать сокет

Симптомы:

  • 502 сразу после миграции;
  • в error.log: «connect() to unix:/run/php/php-fpm.sock failed (13: Permission denied)».

Решение: синхронизировать SocketUser, SocketGroup, SocketMode в php-fpm.socket с пользователем, от которого работает Nginx, и/или с группой, к которой он принадлежит. Убедитесь, что SELinux или AppArmor-политики также не блокируют доступ.

3. Reload приводит к долгим «подвисаниям» вместо 502

Иногда 502 исчезают, но пользователи жалуются на подвисания на несколько секунд. Это ожидаемое следствие: запросы теперь не отваливаются, а стоят в очереди, пока PHP-FPM не поднимется.

Что можно сделать:

  • оптимизировать время запуска PHP-FPM (убрать лишние расширения, тяжёлый автозагрузчик, ненужные init-хуки);
  • делать reload в периоды минимальной нагрузки (ночь, плановое окно развёртывания);
  • при большой нагрузке использовать несколько пулов или серверов PHP-FPM с поочерёдным reload (blue/green на backend-уровне).

Графики мониторинга с уменьшением числа ошибок 502 после настройки PHP-FPM и systemd

Интеграция с Nginx: timeouts и поведение при reload

Чтобы zero downtime reload работал максимально мягко, проверьте конфигурацию Nginx. Базовый фрагмент для PHP может выглядеть так:

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

    fastcgi_pass unix:/run/php/php-fpm.sock;

    fastcgi_connect_timeout 3s;
    fastcgi_send_timeout 60s;
    fastcgi_read_timeout 60s;
}

Ключевой параметр для фаз перезапуска — fastcgi_connect_timeout. Если он слишком маленький, любые задержки при рестарте PHP-FPM будут приводить к ошибкам соединения. Обычно 2–5 секунд достаточно.

Дополнительно полезно настроить:

  • разумные max_fails и fail_timeout в upstream, если вы используете TCP-сокеты через upstream-блок;
  • отдельный location для ping и status PHP-FPM и healthcheck-логику.
Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Мониторинг и проверка zero downtime

После настройки socket activation и staged reload не полагайтесь только на ощущение — проверьте всё под нагрузкой.

Пример плана проверки:

  1. Настройте отдельный тестовый endpoint, который выполняет минимальный PHP-скрипт.
  2. Запустите генератор нагрузки (k6, wrk, ab) с постоянным RPS, например 100–300 запросов в секунду.
  3. Параллельно выполняйте systemctl reload php-fpm.service несколько раз подряд.
  4. Следите за графиками 5xx в Nginx (по логам или метрикам), временем ответа и логами systemd и PHP-FPM.

Если всё настроено правильно, вы увидите либо нулевое число 502 при reload, либо единичные ошибки только в момент, когда специально ломаете конфиг PHP-FPM, а сервис не может стартовать.

В качестве следующего шага можно посмотреть, как организовать перенос сайтов с учётом HSTS и сертификатов, чтобы обновление backend-а сочеталось с корректной работой HTTPS. Об этом в статье миграция доменов, 301 и HSTS при работе с HTTPS.

Итоги

Связка php-fpm + systemd socket activation отлично подходит для тех, кто хочет более предсказуемый и безопасный reload PHP без всплесков 502 и простоя сайта.

Ключевые моменты, которые стоит вынести:

  • создавайте отдельный php-fpm.socket и передавайте управление сокетом systemd, а не PHP-FPM;
  • убедитесь, что php-fpm.service корректно привязан к сокету и не пытается его создавать сам;
  • настройте разумные значения Backlog, fastcgi_connect_timeout и других лимитов, чтобы сокет мог переживать перезапуски сервиса;
  • используйте staged reload: проверка конфигов, мягкий reload, при необходимости плавный restart;
  • перед миграцией и после неё обязательно гоняйте нагрузочные тесты и следите за графиками 5xx.

При такой архитектуре PHP-FPM становится гораздо менее капризным при изменениях конфигурации и версий, а Nginx практически перестаёт видеть окна недоступности backend-а. Немного системного инжиниринга — и устойчивость продакшен-инфраструктуры меньше зависит от того, как часто вы обновляете PHP и его конфигурацию.

Поделиться статьей

Вам будет интересно

Bash one-liners с jq, curl и xargs: практические рецепты для админов OpenAI Статья написана AI (GPT 5)

Bash one-liners с jq, curl и xargs: практические рецепты для админов

Разбираем практические Bash one-liners с jq, curl и xargs: как быстро выдёргивать данные из JSON, пачками дергать HTTP API, провер ...
Nginx + бесплатный cookie-free CDN как origin: пошаговая схема и тонкая настройка OpenAI Статья написана AI (GPT 5)

Nginx + бесплатный cookie-free CDN как origin: пошаговая схема и тонкая настройка

Разбираем, как подключить сайт на Nginx к бесплатному CDN и использовать сервер как origin без сюрпризов. Пошагово настраиваем coo ...
HTTP API-шлюз на Nginx: rate limit, quota и версионирование OpenAI Статья написана AI (GPT 5)

HTTP API-шлюз на Nginx: rate limit, quota и версионирование

Разбираем, как построить лёгкий HTTP API gateway на Nginx без тяжёлых сервис-мешей: маршрутизация по версиям API, ограничение RPS ...