Новинка Виртуальный VDS сервер в Финляндии от 390р
Выберите продукт

Несколько версий PHP на одном VDS: Nginx + несколько PHP‑FPM пулов и изоляция сайтов

Нужно держать на одном сервере проекты на PHP 8.1, 8.2 и 8.3 одновременно и при этом изолировать сайты? Разбираем конфигурацию Nginx с несколькими пулами PHP‑FPM, vhost‑блоки, права, сокеты, типичные ошибки и контроль ресурсов на Ubuntu/Debian.
Несколько версий PHP на одном VDS: Nginx + несколько PHP‑FPM пулов и изоляция сайтов

Сценарий «несколько версий PHP на одном сервере» давно стал нормой: часть продакшена живет на стабильной ветке, свежие проекты требуют новейшую, а легаси — осторожно мигрируется. На Nginx это элегантно решается несколькими пулами php-fpm и привязкой каждого сайта к нужной версии и собственному пользователю. Всё это удобно поднимать на VDS: гибкая конфигурация, выделенные ресурсы и изоляция.

Архитектура: что именно мы настраиваем

Базовая идея проста: для каждой версии PHP создаются один или несколько пулов php-fpm. Каждый пул работает под своим системным пользователем и слушает отдельный Unix‑сокет. В конфигурации Nginx каждый виртуальный хост (vhost) направляет запросы .php на свой сокет и, соответственно, на нужный пул и версию PHP. Главные преимущества такого подхода:

  • Изоляция: процессы PHP одного сайта не получают прав другого, файловую систему разделяют UNIX‑права.
  • Гибкость: разные версии PHP под разные проекты без конфликтов.
  • Точечный тюнинг: параметры php.ini и FPM (pm, лимиты) индивидуально для каждого пула.
  • Надежность: перезагрузка пула не трогает остальные сайты, а opcache изолирован по пулам и версиям.

Золотое правило: изоляцию обеспечивает не «директива в конфиге», а системные пользователи и корректные права на файлы. Все остальное лишь усиливает модель.

FastFox VDS
Облачный VDS-сервер
Виртуальные серверы с быстрым запуском и гибкой конфигурацией от 390₽ / мес
Доступные локации
Россия Нидерланды Финляндия

Среда: 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.

Пример: vhost Nginx с привязкой к сокету PHP‑FPM

Структура каталогов и пользователи для изоляции

Минимально достаточно выделить по системному пользователю на сайт и ограничить права на файлы. Пусть будут два проекта: 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.

Конфигурация пула PHP‑FPM для изоляции сайта

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-сертификаты.
FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой 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 станут рутинной задачей вместо риска для всего сервера.

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

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

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

После одновременной остановки всех узлов Galera обычного запуска MariaDB недостаточно. Сначала найдите сервер с последней транзакц ...
CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

Разберём, почему CoreDNS перезапускается или отвечает SERVFAIL, как обнаружить замкнутую пересылку DNS-запросов, проверить доступн ...
Apache Kafka KRaft на VDS: отдельные controller и broker

Apache Kafka KRaft на VDS: отдельные controller и broker

Соберём новый Kafka-кластер без ZooKeeper на шести Linux VDS: три узла controller образуют отказоустойчивый metadata quorum, а три ...