Сценарий «несколько версий PHP на одном сервере» давно стал нормой: часть продакшена живет на стабильной ветке, свежие проекты требуют новейшую, а легаси — осторожно мигрируется. На Nginx это элегантно решается несколькими пулами php-fpm и привязкой каждого сайта к нужной версии и собственному пользователю. Всё это удобно поднимать на VDS: гибкая конфигурация, выделенные ресурсы и изоляция.
Архитектура: что именно мы настраиваем
Базовая идея проста: для каждой версии PHP создаются один или несколько пулов php-fpm. Каждый пул работает под своим системным пользователем и слушает отдельный Unix‑сокет. В конфигурации Nginx каждый виртуальный хост (vhost) направляет запросы .php на свой сокет и, соответственно, на нужный пул и версию PHP. Главные преимущества такого подхода:
- Изоляция: процессы PHP одного сайта не получают прав другого, файловую систему разделяют UNIX‑права.
- Гибкость: разные версии PHP под разные проекты без конфликтов.
- Точечный тюнинг: параметры
php.iniи FPM (pm, лимиты) индивидуально для каждого пула. - Надежность: перезагрузка пула не трогает остальные сайты, а
opcacheизолирован по пулам и версиям.
Золотое правило: изоляцию обеспечивает не «директива в конфиге», а системные пользователи и корректные права на файлы. Все остальное лишь усиливает модель.
Среда: Ubuntu / Debian, пакеты и версия PHP
На Ubuntu/Debian одновременно можно держать несколько веток PHP: например 8.1, 8.2 и 8.3. Если нужной версии нет в стандартном репозитории вашей системы, используйте backports или специализированный репозиторий для PHP. В остальном процедура одинакова: ставим пакеты phpX.Y-fpm, phpX.Y-cli и требуемые расширения для каждого проекта.
sudo apt update
sudo apt install php8.1-fpm php8.1-cli php8.1-mysql php8.1-xml php8.1-curl php8.1-zip php8.1-gd php8.1-mbstring php8.1-intl
sudo apt install php8.2-fpm php8.2-cli php8.2-mysql php8.2-xml php8.2-curl php8.2-zip php8.2-gd php8.2-mbstring php8.2-intl
sudo apt install php8.3-fpm php8.3-cli php8.3-mysql php8.3-xml php8.3-curl php8.3-zip php8.3-gd php8.3-mbstring php8.3-intl
Убедитесь, что служба FPM для каждой версии поднята:
systemctl status php8.1-fpm
systemctl status php8.2-fpm
systemctl status php8.3-fpm
Если предпочитаете панели, посмотрите сравнение актуальных решений: vds-panels-2025-comparison.

Структура каталогов и пользователи для изоляции
Минимально достаточно выделить по системному пользователю на сайт и ограничить права на файлы. Пусть будут два проекта: site1 на PHP 8.3 и site2 на PHP 8.1.
sudo adduser --disabled-password --gecos "" site1
sudo adduser --disabled-password --gecos "" site2
sudo mkdir -p /var/www/site1/public
sudo mkdir -p /var/www/site2/public
sudo chown -R site1:site1 /var/www/site1
sudo chown -R site2:site2 /var/www/site2
sudo chmod -R o-rwx /var/www/site1 /var/www/site2
Папку public используем как document_root для Nginx, всё остальное — вне веб‑корня. Для большей безопасности вынесите секреты в файлы с правами 640, владельцем — пользователь сайта, группой — веб‑сервер (например, www-data), и запретите чтение остальным.
Пулы PHP‑FPM: один сайт — один пул
Создадим пул для site1 на PHP 8.3. Файл /etc/php/8.3/fpm/pool.d/site1.conf:
[site1]
user = site1
group = site1
listen = /run/php/php8.3-fpm-site1.sock
listen.owner = site1
listen.group = www-data
listen.mode = 0660
pm = ondemand
pm.max_children = 8
pm.process_idle_timeout = 20s
request_terminate_timeout = 300s
catch_workers_output = yes
php_admin_value[open_basedir] = /var/www/site1:/tmp
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 64M
php_admin_value[memory_limit] = 256M
php_admin_value[max_execution_time] = 300
php_admin_flag[log_errors] = on
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,proc_get_status,pcntl_alarm,pcntl_fork,pcntl_waitpid
slowlog = /var/log/php8.3-fpm-site1.slow.log
request_slowlog_timeout = 5s
pm.status_path = /fpm-status
ping.path = /fpm-ping
И пул для site2 на PHP 8.1 — файл /etc/php/8.1/fpm/pool.d/site2.conf:
[site2]
user = site2
group = site2
listen = /run/php/php8.1-fpm-site2.sock
listen.owner = site2
listen.group = www-data
listen.mode = 0660
pm = ondemand
pm.max_children = 6
pm.process_idle_timeout = 20s
request_terminate_timeout = 300s
catch_workers_output = yes
php_admin_value[open_basedir] = /var/www/site2:/tmp
php_admin_value[upload_max_filesize] = 32M
php_admin_value[post_max_size] = 32M
php_admin_value[memory_limit] = 192M
php_admin_value[max_execution_time] = 180
php_admin_flag[log_errors] = on
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,proc_get_status,pcntl_alarm,pcntl_fork,pcntl_waitpid
slowlog = /var/log/php8.1-fpm-site2.slow.log
request_slowlog_timeout = 5s
pm.status_path = /fpm-status
ping.path = /fpm-ping
Перезапускаем оба демона, чтобы появились сокеты в /run/php:
sudo systemctl reload php8.3-fpm
sudo systemctl reload php8.1-fpm
Если директория /run/php отсутствует после перезагрузки сервера, это означает, что она не была создана службой. Как правило, пакеты PHP‑FPM в Debian/Ubuntu создают её сами. Если требуется кастомный путь, убедитесь, что он существует и доступен Nginx.

Nginx: два vhost‑блока, два сокета
Добавим два сайта, каждый на своём сокете. Пример для /etc/nginx/sites-available/site1.conf (ссылка в sites-enabled делается отдельно):
server {
server_name site1.example;
root /var/www/site1/public;
index index.php index.html;
access_log /var/log/nginx/site1.access.log;
error_log /var/log/nginx/site1.error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_read_timeout 300s;
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.3-fpm-site1.sock;
}
client_max_body_size 64m;
}
И /etc/nginx/sites-available/site2.conf:
server {
server_name site2.example;
root /var/www/site2/public;
index index.php index.html;
access_log /var/log/nginx/site2.access.log;
error_log /var/log/nginx/site2.error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_read_timeout 300s;
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.1-fpm-site2.sock;
}
client_max_body_size 32m;
}
Обратите внимание на явный fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name — это снимает проблему «No input file specified.» и позволяет гибко управлять корнем сайта. Привязка к Unix‑сокетам повышает безопасность и обычно даёт меньшие накладные расходы, чем TCP на 127.0.0.1:900x.
CLI‑версии PHP и composer для каждого проекта
Для CLI удобно вызывать конкретную версию напрямую: /usr/bin/php8.3, /usr/bin/php8.1. Так cron‑задачи и деплой‑скрипты всегда исполняются нужной версией.
php8.3 -v
php8.1 -v
crontab -e
# Пример: */5 * * * * cd /var/www/site1 && /usr/bin/php8.3 artisan schedule:run --no-interaction
Если требуется переключать «дефолтный» php для интерактивной сессии, используйте update-alternatives:
sudo update-alternatives --config php
php -v
Тюнинг производительности и памяти
pm = ondemand хорош на VDS со средними нагрузками и пиковыми всплесками: процессы появляются по запросу и завершаются после простоя. Для стабильного потока можно рассмотреть pm = dynamic и настроить pm.max_children, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers. Грубая оценка памяти: сложите RSS одного воркера (с учётом расширений и opcache) и умножьте на pm.max_children; добавьте запас под веб‑сервер и БД. Не забывайте, что opcache выделяется отдельно в php.ini/php_admin_value и существует на уровне процесса пула, то есть не делится между пулами и версиями.
Для горячих CMS ускоряет стабильность включённый opcache.validate_timestamps = 1 с разумным revalidate_freq. Если вы делаете atomic‑деплой, можно оставить validate_timestamps = 0 и исполнять reload пула для инвалидации кэша после релиза. По теме кэширования и компрессии на шаред‑площадках смотрите: shared-hosting-php-opcache-brotli.
Безопасность: изоляция в деталях
- Разделение пользователей: каждый пул работает под своим
uid/gid. Это ключ к файловой изоляции. - Права на сокеты:
listen.owner,listen.group,listen.mode = 0660. Группа веб‑сервера должна иметь доступ к сокету, но не ко всем файлам сайта. - Файловые права: закрываем «other», минимум прав группе. Секреты храним вне
document_root. open_basedir: дополнительная страховка от случайных выходов за корень проекта. Не заменяет файловые права.disable_functions: отключите всё, что не используется (особенно исполнение команд). Для бэкенд‑тасков, где это нужно, заведите отдельный пул.- В Nginx не проксируйте произвольные пути в PHP. Разрешайте только
\.php$и используйтеtry_files $uri =404;. - Скрывайте версию PHP:
expose_php = Offвphp.iniили через админ‑значения пула. Для публичных сайтов включайте HTTPS и HSTS; о миграции на HTTPS и безопасных редиректах читайте: domain-migration-301-hsts-ssl. Если нужен выпуск и установка TLS, используйте наши SSL-сертификаты.
Не используйте один общий пул для всех сайтов, даже если версия PHP совпадает. Разделяйте пулы по проектам — это уменьшает blast radius и упрощает поиск утечек.
Мониторинг и диагностика
В пулах выше мы включили pm.status_path и ping.path. Их удобно опубликовать в Nginx только для локаля или внутренней сети. Минимум — ограничьте доступ по IP.
location = /fpm-ping { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/run/php/php8.3-fpm-site1.sock; allow 127.0.0.1; deny all; }
location = /fpm-status { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/run/php/php8.3-fpm-site1.sock; allow 127.0.0.1; deny all; }
Логи: error_log и access_log Nginx, плюс /var/log/php*-fpm.log и slowlog пула. Для быстрых проверок используйте:
sudo journalctl -u php8.3-fpm -f
sudo journalctl -u php8.1-fpm -f
sudo tail -f /var/log/nginx/site1.error.log
Частые ошибки и как их чинить
- Permission denied к сокету FPM. Симптом:
connect() to unix:/run/php/....sock failed (13). Проверьтеlisten.owner/listen.group/listen.modeпула и пользователя, под которым работает Nginx (обычноwww-data). - No input file specified. Почти всегда неверный
SCRIPT_FILENAME. Убедитесь, что равен$document_root$fastcgi_script_name, иtry_files $uri =404;передаёт реальный файл. - 413 Request Entity Too Large. Увеличьте
client_max_body_sizeв серверном блоке Nginx иpost_max_size/upload_max_filesizeв пуле. - 504 Gateway Timeout. Сверьте
fastcgi_read_timeoutиmax_execution_time/request_terminate_timeout. Если фоновые задачи долгие — вынесите в очередь/воркеры. - Падение пула из‑за памяти. Уменьшите
pm.max_children, оптимизируйте расширения, проверьтеopcache.memory_consumptionи лимиты.
Управление жизненным циклом: деплой, релоды, миграции
При выкладке: сначала синхронизируйте код вне document_root, затем переключите симлинк current на новую версию, и уже после этого выполните reload нужного пула для инвалидации opcache. Это гарантирует отсутствие «гонок» и поломанных инклюдов. Для очередей/воркеров на PHP заведите отдельные пулы и systemd‑юниты — не смешивайте их с пулом фронтенда. Подробно про безостановочную миграцию читайте: zero-downtime-site-migration.
Когда лучше TCP вместо Unix‑сокетов
Unix‑сокеты предпочтительны для одного хоста с Nginx и FPM. Если планируете вынести FPM на соседний хост, используйте TCP‑порт и ограничение firewall. В пределах одной машины TCP может пригодиться для инструментов, которым сложно работать с правами UNIX‑сокетов. По производительности на современных ядрах разницы почти нет, но сокеты всё же чуть быстрее и проще в настройке прав.
Проверочный чек‑лист
- Для каждого сайта есть свой системный пользователь и директория проекта.
- Для каждого сайта создан отдельный пул
php-fpmнужной версии. - Сокеты лежат в
/run/php, права на них выверены. - В
Nginxкаждоеserverуказывает на свойfastcgi_pass. - Параметры загрузок и таймауты согласованы между Nginx и FPM.
- Включены логи и slowlog, статус и пинг ограничены по IP.
- CLI‑команды и cron вызывают конкретные версии
php8.X.
Итог
Многоверсионная конфигурация PHP на одном сервере с Nginx несложна: по пулу на сайт, по версии на пул, чёткие права и минимально необходимые привилегии. Такой подход даёт чистую изоляцию, гибкий тюнинг под нагрузку и предсказуемые релизы. Главное — дисциплина: не смешивать сайты в одном пуле, не давать лишних прав и регулярно проверять логи. Тогда переходы между версиями PHP станут рутинной задачей вместо риска для всего сервера.


