Обновление PHP на продакшене пугает многих разработчиков и сисадминов. Кажется, что это как менять колесо на ходу: чуть ошибся — сайт лежит, клиенты недовольны, телефон разрывается. На практике всё решаемо, если действовать по плану, а не наугад.

Зачем вообще обновлять PHP

PHP 8.3 быстрее PHP 7.4 примерно на 25–30% на реальных нагрузках — это не маркетинг, это замеры Phoronix и Kinsta. Помимо скорости, старые ветки перестают получать патчи безопасности. PHP 7.4 вышел из поддержки ещё в ноябре 2022 года. Если у вас до сих пор крутится 7.4 — это не технический долг, это дыра.

Ещё аргумент: современные библиотеки и фреймворки постепенно отказываются от старых версий. Laravel 11 требует минимум PHP 8.2. Symfony 7 — тоже. Значит, рано или поздно обновляться придётся, лучше по своему расписанию, а не в панике когда что-то сломалось.

Что может пойти не так

Перед тем как лезть в продакшен, нужно понимать основные риски:

Несовместимость кода. Между PHP 7 и PHP 8 много breaking changes. array_key_exists ведёт себя иначе, match — новое ключевое слово, str_contains появился только в 8.0, динамические вызовы на $this убрали. Если кодовая база не обновлялась давно, там могут быть тихие ошибки, которые не падают с исключением, а просто дают неправильный результат.

Расширения PHP. ext-redis, ext-imagick, ext-gd, ext-intl — их нужно перекомпилировать или переустановить под новую версию. Про них часто забывают.

Конфиги php.ini. Значения по умолчанию меняются от версии к версии. error_reporting, precision, serialize_precision — если в коде есть что-то, что завязано на конкретные значения, после обновления поведение изменится.

Composer-зависимости. Пакеты могут требовать конкретную версию PHP в секции require. После обновления composer install может отказаться работать.

Правило номер один: сначала стейджинг

Никогда не обновляй PHP сразу на продакшене. Никогда. Даже если уверен на 100%. Стейджинг должен быть максимально близким к боевому окружению: та же база данных (или дамп), те же переменные окружения, та же версия nginx/apache, те же расширения.

На стейджинге гоняем:

composer diagnose
composer check-platform-reqs
php -l path/to/file.php  # синтаксическая проверка

composer check-platform-reqs скажет, какие пакеты несовместимы с новой версией PHP. Это экономит часы поиска ошибок.

Если есть автотесты — запускаем всё. Если нет — плохо, но хотя бы прошёлся руками по критическим сценариям: авторизация, оплата, отправка форм.

Мультиверсионность PHP — ваш главный инструмент

Самый безопасный способ обновиться — держать на сервере обе версии PHP одновременно. Это не экзотика, это стандартная практика.

На Ubuntu/Debian это делается через репозиторий Ondrej Sury:

add-apt-repository ppa:ondrej/php
apt update
apt install php8.3 php8.3-fpm php8.3-cli php8.3-mysql php8.3-redis php8.3-gd

Теперь у вас есть и PHP 7.4 (или 8.1), и PHP 8.3. Они работают параллельно через разные сокеты FPM:

  • /var/run/php/php7.4-fpm.sock
  • /var/run/php/php8.3-fpm.sock

Nginx переключается между ними на уровне конфига. Это ключевой момент.

Переключение в Nginx

Типичный конфиг до обновления:

location ~ \.php$ {
    fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

Меняем одну строку:

location ~ \.php$ {
    fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

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

nginx -t
nginx -s reload

nginx -s reload — это graceful reload. Nginx дочитывает текущие запросы, а новые уже идут через новый воркер. Даунтайма нет совсем.

Если что-то пошло не так — меняем строку обратно и снова nginx -s reload. Откат занимает 30 секунд.

Apache + PHP-FPM

С Apache принцип тот же, только синтаксис другой. Если используете mod_php — это сложнее, потому что модуль загружается при старте Apache. Лучше перейти на PHP-FPM + mod_proxy_fcgi, это даёт такую же гибкость как в Nginx.

Пример виртуального хоста Apache с FPM:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example

    <FilesMatch \.php$>
        SetHandler "proxy:unix:/var/run/php/php8.3-fpm.sock|fcgi://localhost"
    </FilesMatch>
</VirtualHost>

Перезагрузка тоже graceful:

apachectl graceful

Конфиги PHP-FPM

Не забудьте перенести настройки из старого пула в новый. Файл пула находится в /etc/php/8.3/fpm/pool.d/www.conf. Смотрите на:

  • pm.max_children — максимальное количество воркеров
  • pm.start_servers, pm.min_spare_servers, pm.max_spare_servers
  • request_terminate_timeout — таймаут выполнения запроса
  • env[...] — переменные окружения, если прокидываете их через FPM

И обязательно сверьте /etc/php/8.3/fpm/php.ini с текущим /etc/php/7.4/fpm/php.ini. Особенно memory_limit, upload_max_filesize, post_max_size, max_execution_time.

Расширения: не забыть и не сломать

Список расширений текущей версии PHP:

php7.4 -m

Список расширений новой:

php8.3 -m

Сравниваем, доустанавливаем то, чего нет:

apt install php8.3-redis php8.3-imagick php8.3-intl php8.3-bcmath php8.3-zip

PECL-расширения (например, xdebug или нестандартный swoole) нужно перекомпилировать отдельно. Для xdebug:

pecl -d php_suffix=8.3 install xdebug

После установки добавляем в /etc/php/8.3/fpm/conf.d/99-xdebug.ini.

Supervisor, очереди и CLI-процессы

Если у вас крутятся воркеры очередей (Laravel Horizon, Symfony Messenger, Artisan queue:work) — они используют свою версию PHP, не ту, что в FPM. После переключения Nginx FPM на 8.3 воркеры продолжают работать на 7.4.

Меняем бинарь в конфиге Supervisor:

[program:laravel-worker]
command=/usr/bin/php8.3 /var/www/artisan queue:work

Перезагружаем:

supervisorctl reread
supervisorctl update
supervisorctl restart laravel-worker:*

Аналогично для крон-задач в crontab — проверьте, что там явно указан путь к нужной версии:

* * * * * /usr/bin/php8.3 /var/www/artisan schedule:run

Мониторинг после переключения

После nginx -s reload не уходите пить кофе. Смотрите:

Логи PHP-FPM:

tail -f /var/log/php8.3-fpm.log

Логи ошибок Nginx:

tail -f /var/log/nginx/error.log

Логи приложения — там могут быть PHP Notices и Warnings, которые не крашат сайт, но сигнализируют о проблемах.

Через 15–30 минут смотрим метрики: время ответа, процент ошибок (5xx), утилизацию CPU и памяти. Если что-то ухудшилось — откатываемся и разбираемся.

Постепенное переключение через несколько виртуальных хостов

Если сайт большой и страшно переключать всё сразу — можно сделать канареечный деплой на уровне PHP. Создаём второй виртуальный хост для тестовой группы пользователей или внутреннего IP:

server {
    listen 80;
    server_name beta.example.com;

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        ...
    }
}

Прогоняем часть трафика через beta, смотрим результат, потом переключаем основной домен.

Как это выглядит в проектах студии

В REEXY при обслуживании корпоративных сайтов обновление PHP — часть планового технического обслуживания. Мы всегда сначала разворачиваем стейджинг, прогоняем тесты, и только после проверки переключаем продакшен. Весь процесс занимает от 2 до 4 часов в зависимости от сложности проекта, при этом сайт не уходит в офлайн ни на секунду.

Что делать, если что-то сломалось

Откат:

# Меняем обратно в nginx.conf
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;

# Перезагружаем
nginx -s reload

Собираем логи, разбираемся с конкретной ошибкой. Чаще всего это:

  • Устаревшая функция — заменяем или добавляем полифилл
  • Пакет несовместим с PHP 8.3 — ищем обновление или форк
  • Тип-несоответствие — PHP 8 строже относится к типам, где 7.4 молчал

Для поиска проблем отлично работает phpstan или rector. Rector умеет автоматически переписывать код для совместимости с новой версией:

composer require rector/rector --dev
vendor/bin/rector process src --dry-run

Флаг --dry-run покажет, что изменится, не трогая файлы.

Краткий чеклист

  1. Обновить и протестировать на стейджинге
  2. Запустить composer check-platform-reqs на новой версии
  3. Установить новую версию PHP + FPM + все нужные расширения
  4. Скопировать и настроить php.ini и pool.conf
  5. Переключить Nginx/Apache на новый FPM-сокет
  6. Сделать graceful reload веб-сервера
  7. Обновить Supervisor и crontab
  8. Мониторить логи и метрики 30 минут
  9. Убедиться, что старая версия PHP ещё не удалена — на случай отката

Обновление PHP — это не страшно. Страшно делать это без плана и без возможности откатиться. Если всё подготовлено заранее, переключение занимает меньше минуты, а сайт продолжает работать без перерыва.