Виртуальный хост Nginx — это блок server, который связывает доменное имя, корень сайта и обработчик PHP в один конфиг. На VDS с несколькими сайтами именно эти блоки определяют, какой запрос куда попадёт.
Настройка виртуального хоста Nginx
Создайте отдельный файл конфигурации для сайта — не редактируйте default напрямую, если сайтов будет больше одного:
nano /etc/nginx/sites-available/yourdomain.com
Содержимое файла — минимальный рабочий блок для сайта на PHP:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/yourdomain.com;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
Активируйте конфигурацию симлинком в sites-enabled — Nginx читает конфиги именно из этой папки, а sites-available служит только хранилищем:
ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Несколько сайтов на одном IP
Nginx выбирает нужный блок по заголовку Host из запроса, сверяя его со списком в server_name. Если ни один блок не совпал, срабатывает блок с default_server в директиве listen — без него первый по порядку чтения файлов конфиг становится дефолтным неявно, что иногда приводит к неожиданному результату при добавлении нового сайта.
server {
listen 80 default_server;
server_name _;
return 444;
}
Такой блок-заглушка с return 444; обрывает соединение для запросов с любым доменом, который не настроен явно, — полезная защита от ботов, сканирующих IP-адреса напрямую, в обход DNS.
Gzip-сжатие ответов
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_min_length отсекает сжатие совсем маленьких файлов, где выигрыш меньше накладных расходов на упаковку, а gzip_comp_level в диапазоне 5–6 — разумный баланс между экономией трафика и нагрузкой на CPU; уровень 9 почти не выигрывает в размере, но заметно дороже по процессору.
Кэширование статики браузером
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
Разбор типичных ошибок
Ошибка nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use) означает, что порт 80 уже занят другим процессом — чаще всего это Apache, оставшийся включённым, или второй запущенный экземпляр Nginx. Проверьте занятые порты командой ss -tulpn | grep :80 и остановите лишний процесс.
502 Bad Gateway — Nginx не получил ответ от бэкенда, указанного в fastcgi_pass или proxy_pass. Точная причина всегда в /var/log/nginx/error.log: строка connect() to unix:/run/php/php8.2-fpm.sock failed (13: Permission denied) указывает на права доступа к сокету, а (111: Connection refused) — на то, что PHP-FPM попросту не запущен.
404 на всех страницах, кроме главной в проектах с «красивыми» URL обычно значит, что директива try_files не настроена на передачу запроса в index.php — Nginx честно ищет файл на диске с именем маршрута и не находит его.
Сравнение способов маршрутизации в Nginx
| Подход | Где задаётся | Гибкость | Типичный случай |
|---|---|---|---|
| try_files | В location / | Средняя, порядок фиксирован | Отдача статики с фолбэком на index.php |
| rewrite | В server или location | Высокая, регулярные выражения | Старые ссылки, ЧПУ без фреймворка |
| return | В server или location | Низкая, прямой ответ | Редиректы, обрыв соединения (444) |
| proxy_pass | В location | Высокая, полный проброс на бэкенд | Node.js, Python, обратный прокси |
Более глубокий разбор приоритета типов location и того, какой блок сработает при пересечении правил, — в статье про приоритет location в Nginx (там же разобраны примеры конфликтов конфигурации).
Когда голого Nginx-конфига недостаточно
- Сайту нужен HTTPS — сертификат и редирект с HTTP добавляются отдельно, это описано в статье про установку SSL через Certbot.
- Много одинаковых сайтов на одном сервере — вместо копирования конфига под каждый домен удобнее вынести общие блоки в
include-файлы. - Нужна защита от перебора или DDoS на уровне запросов — здесь стандартный
server-блок не поможет, нужныlimit_req,limit_connили внешний WAF. - Бэкенд — не PHP-FPM, а отдельное приложение на своём порту (Node.js, Python, Go) — вместо
fastcgi_passиспользуетсяproxy_passс отдельным набором заголовков.
Проверка результата
- Проверьте синтаксис перед каждым изменением:
nginx -tдолжен вернутьsyntax is okиtest is successful. - Откройте сайт по домену и по IP отдельно — если по домену работает, а по IP видно чужой сайт, значит верно настроен
server_name, а если по IP видно этот же сайт безdefault_server, стоит это проверить. - Проверьте заголовки ответа:
curl -I http://yourdomain.com— заголовокContent-Encoding: gzipподтвердит, что сжатие реально работает, а не просто включено в конфиге. - Просмотрите access-лог на предмет 404 и 502 сразу после деплоя:
tail -f /var/log/nginx/access.logпокажет реальные запросы пользователей, а не только тестовые из терминала.