В этой статье разберём практический сценарий: у нас есть PHP‑приложение (фреймворк или «самопис», не принципиально), и нужно развернуть его на новом VDS. Никаких панелей — только SSH и базовый набор инструментов. Цель: получить предсказуемый, управляемый стек с PHP, Nginx и MySQL, корректно работающий DNS и понятный процесс деплоя.
1. Исходные данные и базовая модель
Предположим, у нас есть:
- VDS с Debian/Ubuntu (пример будем показывать на Debian/Ubuntu‑подобных системах);
- домен
example.com, которым вы управляете через панель регистратора; - PHP‑приложение в репозитории Git (или хотя бы в виде архива);
- нужна связка: Nginx → PHP‑FPM → PHP‑код + база MySQL/MariaDB.
Наша минимальная задача:
- подготовить VDS: SSH, обновления, базовая безопасность;
- настроить DNS: A‑запись на сервер;
- установить Nginx, PHP‑FPM, MySQL/MariaDB;
- подготовить виртуальный хост, директорию проекта и пользователя деплоя;
- выполнить деплой и проверить работу;
- разобрать элементарную оптимизацию под типичные PHP‑нагрузки.
2. Первичное подключение к VDS и подготовка системы
2.1. Подключение по SSH
После получения VDS вы получаете IP‑адрес и пароль root или пользователя с правами sudo. Подключаемся:
ssh root@YOUR_VDS_IP
Для начала стоит поменять пароль (или сразу настроить авторизацию по ключам, что намного безопаснее):
passwd
2.2. Обновление системы и базовый набор пакетов
Перед любыми установками обновляем репозитории и ставим базовый набор утилит:
apt update
apt upgrade -y
apt install -y sudo vim htop curl git unzip
Создадим отдельного пользователя для управления приложением, чтобы не работать под root:
adduser deploy
usermod -aG sudo deploy
Дальше логинимся под пользователем deploy и стараемся root использовать только при необходимости.
2.3. Часовой пояс, hostname, базовые настройки
Приводим часовой пояс к вашему региону — важно для логов и кронов:
sudo timedatectl set-timezone Europe/Moscow
Проверяем hostname и при необходимости задаём осмысленный:
hostnamectl
sudo hostnamectl set-hostname app-vds-1

3. DNS: связываем домен и VDS
Чтобы сайт открывался по домену, а не по IP, нужно настроить DNS. Минимум — A‑записи вида:
example.com. 300 IN A 203.0.113.10
www.example.com. 300 IN A 203.0.113.10
Где 203.0.113.10 — IP вашего VDS. TTL 300 (5 минут) удобен на этапе настройки и миграций.
После изменения DNS ждём применения (обычно 5–15 минут) и проверяем:
dig +short example.com
ping -c 4 example.com
Если ответы приходят с IP вашего VDS, DNS‑часть базово готова. Если используете отдельный почтовый сервис, не забудьте корректно настроить MX‑записи и SPF/DMARC — детально про это можно почитать в статье о DNS‑записях для авторизации почты.
4. Установка PHP, Nginx и MySQL/MariaDB
4.1. Выбор версий PHP и MySQL
Для нового проекта имеет смысл сразу использовать актуальную ветку PHP (например, PHP 8.2+). Для MySQL можно взять либо MySQL 8, либо MariaDB 10.6+ (зависит от требований приложения и привычки). Старайтесь не ставить устаревшие версии: вы потеряете по безопасности и производительности.
4.2. Установка Nginx и PHP‑FPM
Устанавливаем веб‑сервер и PHP‑FPM с популярными расширениями:
sudo apt install -y nginx
sudo apt install -y php-fpm php-mysql php-cli php-xml php-mbstring php-curl php-zip
Проверяем версии и статусы сервисов:
php -v
nginx -v
systemctl status php*-fpm
systemctl status nginx
Важно, чтобы PHP работал через FPM, а не как модуль Apache: на VDS связка Nginx + PHP‑FPM обычно даёт лучшую управляемость и предсказуемую производительность.
4.3. Установка и первичная настройка MySQL/MariaDB
Устанавливаем сервер баз данных:
sudo apt install -y mariadb-server
После установки запускаем скрипт базовой безопасности:
sudo mysql_secure_installation
Скрипт предложит:
- задать пароль root в MySQL (если он ещё не установлен);
- удалить анонимные аккаунты;
- запретить root‑логин не с localhost;
- удалить тестовую базу;
- перезагрузить таблицы привилегий.
На все вопросы, кроме, возможно, пароля, обычно отвечаем «Yes».
4.4. Создание базы и пользователя для PHP‑приложения
Заходим в MySQL под root:
sudo mysql
Создаём базу и отдельного пользователя с минимальными правами:
CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'S3cureP4ss!';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Используйте свой пароль, не из примера. Его мы позже пропишем в конфиге PHP‑приложения.
5. Структура проекта и пользователь деплоя
5.1. Где хранить код и статику
На VDS удобно разделять:
/var/www/example.com/current— текущий релиз (symlink на папку релиза);/var/www/example.com/releases— каталоги с релизами;/var/www/example.com/shared— общие файлы (конфиги, логи, storage и т.п.).
Для начала можно обойтись упрощённой схемой: просто /var/www/example.com, но лучше с самого начала закладывать структуру под дальнейший автоматизированный деплой.
5.2. Права и владелец файлов
Предположим, владелец кода — пользователь deploy, а PHP‑FPM работает от пользователя www-data. Нужна аккуратная настройка прав, чтобы и деплой, и PHP могли читать/писать, где нужно.
Создадим директорию под проект:
sudo mkdir -p /var/www/example.com
sudo chown -R deploy:www-data /var/www/example.com
sudo chmod 2755 /var/www/example.com
Бит 2 (setgid) на директории помогает сохранять группу для создаваемых файлов и директорий.
6. Настройка PHP‑FPM под приложение
По умолчанию PHP‑FPM поднимает пул www. Для изоляции приложений на одном VDS лучше делать отдельные пулы, но в базовом сценарии можно использовать дефолтный пул, аккуратно настроив основные параметры.
6.1. Основные параметры пула PHP‑FPM
Открываем конфигурацию пула (имя файла и путь могут отличаться в зависимости от дистрибутива и версии PHP):
sudo vim /etc/php/8.2/fpm/pool.d/www.conf
Нас интересует:
userиgroup— от кого работает PHP‑код;listen— сокет или порт;- режим управления процессами:
pm,pm.max_children,pm.max_requests.
Типичная базовая настройка (фрагмент, показываем только суть):
user = www-data
group = www-data
listen = /run/php/php8.2-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 500
После правок:
sudo systemctl reload php8.2-fpm
Конкретные числа зависят от ресурсов VDS: для 1–2 ГБ RAM не стоит ставить слишком много процессов, иначе под нагрузкой сервер уйдёт в своп.
6.2. Общие PHP‑настройки (php.ini)
Из базового полезно проверить:
memory_limit— сколько памяти можно съесть одному PHP‑процессу;upload_max_filesizeиpost_max_size— если приложение принимает большие файлы;max_execution_time— верхний предел для долгих операций;date.timezone— чтобы не было проблем с датами.
Правится обычно файл вида /etc/php/8.2/fpm/php.ini:
sudo vim /etc/php/8.2/fpm/php.ini
И не забываем перезапустить PHP‑FPM после изменений.

7. Настройка Nginx: виртуальный хост для PHP
7.1. Базовый server{} для сайта
Создадим файл конфигурации виртуального хоста, например:
sudo vim /etc/nginx/sites-available/example.com.conf
Минимальный вариант для PHP‑приложения (без HTTPS, только чтобы подняться и проверить связку):
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.php index.html;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log warn;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_index index.php;
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|webp|svg)$ {
expires 7d;
access_log off;
}
}
Дальше активируем конфиг и проверяем:
sudo ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Если проверка (nginx -t) успешна, виртуальный хост поднялся.
7.2. Проверочный info.php
Чтобы убедиться, что связка Nginx → PHP‑FPM работает, создадим простой файл:
mkdir -p /var/www/example.com/public
echo '<?php phpinfo();' > /var/www/example.com/public/info.php
sudo chown -R deploy:www-data /var/www/example.com
Теперь открываем в браузере адрес вида http://example.com/info.php. Если видите страницу с информацией о PHP — стек работает. Не забудьте удалить этот файл после проверки, чтобы не светить конфиг PHP в проде.
8. Подготовка окружения приложения: конфиг, зависимости, права
8.1. Конфигурация подключения к MySQL
Большинство PHP‑фреймворков и CMS имеют свой файл конфигурации БД: .env, config.php, database.php и т.д. Вам нужно в нём прописать:
- host:
127.0.0.1(илиlocalhost); - database:
appdb; - user:
appuser; - password: ваш надёжный пароль;
- charset:
utf8mb4.
Следите, чтобы конфиг не был доступен напрямую через веб‑сервер (обычно они лежат за пределами public или закрыты правилами маршрутизации).
8.2. Composer‑зависимости
Если приложение использует Composer, ставим его и подтягиваем зависимости на сервере:
cd /var/www/example.com
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php --install-dir=/usr/local/bin --filename=composer
rm composer-setup.php
composer install --no-dev --optimize-autoloader
В продакшн‑окружении почти всегда включаем --no-dev и --optimize-autoloader. Если интернет на сервере ограничен, можно собирать vendor‑папку локально и загружать её вместе с кодом.
8.3. Права на каталоги cache/logs/uploads
Чтобы приложение корректно писало кэш, логи и загружаемые файлы, даём права пользователю, от которого работает PHP‑FPM, обычно www-data:
sudo chown -R deploy:www-data /var/www/example.com
sudo find /var/www/example.com -type f -exec chmod 640 {} \;
sudo find /var/www/example.com -type d -exec chmod 750 {} \;
Отдельно можно чуть ослабить права для каталогов, куда идёт запись из веб‑приложения (например, storage, var, uploads):
sudo chmod -R 770 /var/www/example.com/storage
Конкретные пути зависят от вашей CMS/фреймворка.
9. Деплой: как удобно накидывать релизы на VDS
9.1. Ручной деплой через Git
Самый простой вариант — клонировать репозиторий напрямую на VDS:
cd /var/www/example.com
sudo -u deploy git clone git@github.com:you/yourapp.git .
Дальше при обновлении:
cd /var/www/example.com
sudo -u deploy git pull
composer install --no-dev --optimize-autoloader
php artisan migrate
Название команд зависят от фреймворка (вместо artisan может быть bin/console, свои скрипты и т.д.).
9.2. Архив или rsync
Если Git на сервере использовать не хотите, можно деплоить через rsync или архив:
# на локальной машине
rsync -avz ./project/ deploy@example.com:/var/www/example.com/
После выкладки — тот же набор шагов: установка зависимостей, миграции БД, очистка кэшей, прогрев конфигурации.
9.3. Минимум безопасности для деплоя
Не храните пароль от MySQL и прочие секреты в репозитории в открытом виде. Используйте:
- отдельные
.env‑файлы на сервере, исключённые из Git; - разные учётные данные для prod/stage/dev;
- SSH‑ключи с ограниченным доступом под пользователя
deploy.
10. Проверка работоспособности PHP, MySQL и DNS
10.1. Локальная проверка сервисов
Проверяем, что все нужные сервисы запущены и слушают нужные порты/сокеты:
systemctl status nginx
systemctl status php8.2-fpm
systemctl status mariadb
ss -tulpn | grep -E '80|3306'
Если MySQL слушает только 127.0.0.1:3306 — это нормально и даже хорошо: доступ к БД только локальный, из PHP‑приложения.
10.2. Тест подключения PHP → MySQL
Можно сделать маленький тестовый скрипт, который подключается к БД:
echo '<?php
$mysqli = new mysqli("127.0.0.1", "appuser", "S3cureP4ss!", "appdb");
if ($mysqli->connect_errno) {
echo "Failed to connect to MySQL: " . $mysqli->connect_error;
} else {
echo "OK";
}
' > /var/www/example.com/public/dbtest.php
Откройте в браузере адрес вида http://example.com/dbtest.php. Если видите «OK» — связка PHP + MySQL настроена корректно. Не забудьте удалить этот файл после проверки.
10.3. Проверка DNS и host‑заголовков
Иногда сайт по IP открывается, а по домену — нет. В таком случае проверяем, какой сервер отвечает и какой host уходит в запросе:
curl -v http://example.com/
Смотрите на блоки Connected to и > Host: example.com. Если всё указывает на ваш VDS, а в логах Nginx появляются обращения — проблема либо в приложении, либо в конфиге виртуального хоста. При смене регистратора или перенастройке домена может пригодиться материал о том, как аккуратно переносить домены и DNS, например статья о переносе домена и корректной работе DNS.
11. Базовая оптимизация: где обычно «болит»
После того как сайт заработал, важно не останавливаться на «как‑то работает». Несколько типичных мест, которые имеет смысл проверить и подкрутить ещё на старте.
11.1. Лимиты Nginx и размеры запросов
Если приложение принимает загрузку файлов, убедитесь, что размер запроса не режется на уровне Nginx или PHP. В конфиге server или глобально в nginx.conf проверьте:
client_max_body_size 20m;
В php.ini — параметры upload_max_filesize и post_max_size должны быть не меньше, чем максимально ожидаемый размер файла.
11.2. Кэширование статики
Чтобы не забивать PHP‑процессы отдачей картинок и CSS, правильно выставляем заголовки кэширования для статических файлов (выше мы уже задействовали expires 7d). Можно добавить:
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|webp)$ {
expires 7d;
add_header Cache-Control "public";
}
Это уменьшит нагрузку на VDS при повторных посещениях сайта.
11.3. PHP‑опкэш
Убедитесь, что включён opcache — без него PHP‑приложение будет каждый запрос компилировать скрипты заново, что особенно критично на нагруженных проектах.
В php.ini или отдельном файле конфигурации opcache проверьте ключевые параметры:
opcache.enable=1;opcache.memory_consumption— достаточно ли памяти под кеш кода;opcache.max_accelerated_files— достаточно ли слотов под кэшируемые файлы.
11.4. Базовая настройка MySQL/MariaDB под VDS
Конфигурация MySQL по умолчанию часто не оптимальна под небольшой VDS. Даже пара настроек даёт заметную прибавку:
innodb_buffer_pool_size— 30–50% от RAM для небольшого сервера, если БД важна;max_connections— не завышайте без необходимости, иначе при пике легко «съесть» всю память;query_cache_typeиquery_cache_sizeдля MySQL 5.7 и ниже лучше выключить (на новых версиях он и так удалён).
Эти параметры задаются в my.cnf, после чего требуется перезапуск службы MySQL.
12. Логи и диагностика: куда смотреть при проблемах
Чтобы не тратить часы на «оно не работает», полезно с самого начала знать, где живут логи:
- логи Nginx:
/var/log/nginx/access.logи/var/log/nginx/error.log(или разнесённые по виртуальным хостам); - логи PHP‑FPM:
/var/log/php8.2-fpm.logи, при включённомphp_admin_value[error_log], логи конкретного пула; - ошибки PHP в приложении: в зависимости от фреймворка — обычно в
storage/logsили аналогичных каталогах; - логи MySQL/MariaDB:
/var/log/mysql/error.logили системный журнал черезjournalctl.
При первых проблемах можно ориентироваться на типичные симптомы:
- «502 Bad Gateway» — смотрим error‑лог Nginx и логи PHP‑FPM;
- «500 Internal Server Error» — чаще всего ошибка в приложении/конфиге, проверяем логи PHP и приложения;
- «Connection refused» к БД — MySQL не запущен или неверные креды/хост;
- «404 Not Found» при «красивых» URL — проверьте
try_filesи маршрутизацию в Nginx.
13. Что ещё имеет смысл сделать после первого запуска
Когда базовый стек работает, а приложение отвечает по домену, можно переходить к следующему уровню зрелости:
- включить HTTPS с нормальным сертификатом и настроить редиректы HTTP → HTTPS (для продакшена стоит сразу использовать боевые SSL-сертификаты);
- поднять бэкапы БД и файлов (хотя бы ежедневные);
- подключить мониторинг ресурсов VDS (нагрузка CPU, RAM, диск, доступность сайта);
- вынести деплой в скрипты или CI/CD, чтобы не нажимать каждый раз руками.
Но всё это уже строится поверх базового каркаса, который мы разобрали: рабочий PHP‑стек (Nginx + PHP‑FPM + MySQL), аккуратно настроенный VDS и предсказуемый процесс развертывания приложения.
Если вы раньше работали только с общим виртуальным хостингом, первый переход на VDS может показаться сложным. Однако все шаги логичны и воспроизводимы: подготовить сервер, настроить PHP и веб‑сервер, привязать базу и домен, наладить деплой и минимальную оптимизацию. Дальше останется только углубляться в тонкую настройку под ваш конкретный проект и нагрузку.


