Nginx: virtual hosts и структура сайтов — как обслуживать несколько проектов правильно
Часть 2 серии про Nginx
В первой части мы разобрали, как устроен Nginx и где живут конфигурации.
Теперь — самое важное для практики: virtual hosts (server blocks), несколько сайтов на одном сервере и правильная структура каталогов.
Введение
Если ты уже знаешь, где лежит nginx.conf и что такое server блок, пора переходить к реальной практике — настройке нескольких сайтов на одном сервере.
В этой статье разберём:
- как Nginx определяет, какой сайт обрабатывать;
- где хранить конфиги для разных сайтов;
- правильную структуру каталогов проектов;
- типовые ошибки и как их избежать;
- практические примеры для Ubuntu/Debian и CentOS/Rocky.
Всё строго по официальной документации Nginx, без фантазий.
Задача, которую решает virtual host
Типичная ситуация:
- один сервер;
несколько доменов:
site1.ru
site2.ru
api.site3.ru;
каждый сайт — отдельный проект, своя логика, свои логи.
👉 Virtual host (server block) позволяет Nginx понять,
какому сайту принадлежит входящий запрос.
Как Nginx выбирает нужный сайт
Алгоритм простой:
- Клиент подключается к IP и порту (80 или 443)
- В HTTP-заголовке передаётся Host
- Nginx ищет server_name, который совпадает
- Если не нашёл — берёт первый server block как default
Базовый server block (минимум)
Минимальная рабочая конфигурация для одного сайта:
server {
listen 80;
server\_name example.com www.example.com;
root /var/www/example.com/public;
index index.html index.php;
location / {
try\_files $uri $uri/ =404;
}
}
Что здесь происходит
- listen 80 — слушаем HTTP на порту 80
- server_name example.com www.example.com — домены, для которых применяется этот блок
- root /var/www/example.com/public — корневая директория сайта
- index index.html index.php — файлы, которые Nginx ищет при запросе к директории
- try_files $uri $uri/ =404 — проверяет существование файла, затем директории, иначе возвращает 404
Полный пример с логами
server {
listen 80;
server\_name example.com www.example.com;
root /var/www/example.com/public;
index index.html index.php;
access\_log /var/www/example.com/logs/access.log;
error\_log /var/www/example.com/logs/error.log;
location / {
try\_files $uri $uri/ =404;
}
}
Где хранить конфиги сайтов (важно)
Ubuntu / Debian (рекомендуемый подход)
/etc/nginx/
├── sites-available/
│ ├── site1.conf
│ └── site2.conf
└── sites-enabled/
├── site1.conf -> ../sites-available/site1.conf
└── site2.conf -> ../sites-available/site2.conf
Активация сайта:
Создаём симлинк
ln -s /etc/nginx/sites-available/site1.conf /etc/nginx/sites-enabled/
Проверяем конфигурацию
nginx -t
Если проверка прошла успешно, перезагружаем
systemctl reload nginx
Пример полного конфига для Ubuntu/Debian
Создаём файл /etc/nginx/sites-available/mysite.conf:
server {
listen 80;
server\_name mysite.ru www.mysite.ru;
root /var/www/mysite.ru/public;
index index.html;
access\_log /var/www/mysite.ru/logs/access.log;
error\_log /var/www/mysite.ru/logs/error.log;
location / {
try\_files $uri $uri/ =404;
}
}
Активируем:
ln -s /etc/nginx/sites-available/mysite.conf /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Отключение сайта
Удаляем симлинк
rm /etc/nginx/sites-enabled/mysite.conf
Проверяем и перезагружаем
nginx -t && systemctl reload nginx
CentOS / Alma / Rocky
/etc/nginx/conf.d/
├── site1.conf
├── site2.conf
└── api.site3.conf
Тут всё проще — файл есть → сайт активен. Симлинки не нужны.
Пример конфига для CentOS/Rocky
Создаём файл /etc/nginx/conf.d/mysite.conf:
server {
listen 80;
server\_name mysite.ru www.mysite.ru;
root /var/www/mysite.ru/public;
index index.html;
access\_log /var/www/mysite.ru/logs/access.log;
error\_log /var/www/mysite.ru/logs/error.log;
location / {
try\_files $uri $uri/ =404;
}
}
Проверяем и перезагружаем:
nginx -t && systemctl reload nginx
Отключение сайта в CentOS/Rocky
Переименовываем файл (добавляем .disabled)
mv /etc/nginx/conf.d/mysite.conf /etc/nginx/conf.d/mysite.conf.disabled
Или удаляем
rm /etc/nginx/conf.d/mysite.conf
nginx -t && systemctl reload nginx
Правильная структура каталогов сайтов
❌ Плохо (часто вижу в проде)
/var/www/
├── site1
├── site2
├── site3
Без логики, всё вперемешку.
✅ Хорошо (чётко и масштабируемо)
/var/www/
├── site1.ru/
│ ├── public/
│ ├── logs/
│ └── releases/ (если есть деплой)
├── site2.ru/
│ ├── public/
│ └── logs/
Почему public — must have
- безопасность (нет доступа к .env, конфигам, vendor);
- единый подход для PHP, Python, Node;
- удобно для CI/CD.
Root vs alias — не путай
Это критически важно понимать разницу, иначе будут 404 ошибки.
root (90% случаев)
Директива root добавляет путь из location к указанному пути:
location / {
root /var/www/site1.ru/public;
}
Как работает:
- URL: /img/logo.png
- Файл на диске: /var/www/site1.ru/public/img/logo.png
- Nginx берёт root + location + URI
Пример с вложенным location
server {
root /var/www/site1.ru/public;
location / {
try\_files $uri $uri/ =404;
}
location /static/ {
root уже определён выше, путь будет:
/var/www/site1.ru/public/static/file.css
expires 30d;
}
}
alias (осторожно)
Директива alias заменяет путь из location:
location /media/ {
alias /data/uploads/;
}
Как работает:
- URL: /media/file.jpg
- Файл на диске: /data/uploads/file.jpg
- Nginx берёт alias + URI (без /media/)
❌ Частая ошибка с alias
НЕПРАВИЛЬНО
location /media {
alias /data/uploads/;
}
При запросе /media/file.jpg Nginx будет искать /data/uploads//file.jpg (двойной слэш).
✅ Правильно
location /media/ {
alias /data/uploads/;
}
Или:
location /media {
alias /data/uploads;
}
👉 Правило: если в location есть завершающий слэш, он должен быть и в alias.
Когда использовать alias
Используй alias, когда нужно отдать файлы из другой директории, не связанной со структурой сайта:
Статические файлы из отдельного хранилища
location /uploads/ {
alias /mnt/storage/uploads/;
}
Или файлы из другого проекта
location /legacy/ {
alias /var/www/old-site/public/;
}
Несколько сайтов на одном IP
Один сервер, один IP, несколько доменов — классическая задача.
Пример конфигурации
Сайт 1
server {
listen 80;
server\_name site1.ru www.site1.ru;
root /var/www/site1.ru/public;
index index.html;
access\_log /var/www/site1.ru/logs/access.log;
error\_log /var/www/site1.ru/logs/error.log;
location / {
try\_files $uri $uri/ =404;
}
}
Сайт 2
server {
listen 80;
server\_name site2.ru www.site2.ru;
root /var/www/site2.ru/public;
index index.html;
access\_log /var/www/site2.ru/logs/access.log;
error\_log /var/www/site2.ru/logs/error.log;
location / {
try\_files $uri $uri/ =404;
}
}
API поддомен
server {
listen 80;
server\_name api.site3.ru;
root /var/www/api.site3.ru/public;
index index.php;
access\_log /var/www/api.site3.ru/logs/access.log;
error\_log /var/www/api.site3.ru/logs/error.log;
location / {
try\_files $uri $uri/ =404;
}
}
Как это работает
- Клиент отправляет запрос на IP сервера
- В HTTP-заголовке Host передаётся домен (например, Host: site1.ru)
- Nginx ищет server_name, который совпадает с Host
- Если совпадение найдено — применяется конфигурация этого блока
- Если не найдено — используется default server (или первый блок)
Работает за счёт HTTP-заголовка Host (RFC 7230).
Default server — защита от мусора
Обязательно настраивай default server, иначе Nginx будет использовать первый server блок как дефолтный.
Зачем нужен default_server
Без default server:
- Запросы по IP напрямую попадают в первый server блок
- Запросы с неизвестными доменами тоже попадают туда
- Лишние логи, лишняя нагрузка
Правильная настройка
server {
listen 80 default\_server;
server\_name \_;
return 444;
}
Что здесь происходит:
- listen 80 default_server — этот блок становится дефолтным для порта 80
- server_name _ — специальное значение, означает "любой домен, который не совпал"
- return 444 — закрывает соединение без ответа (экономит трафик)
Альтернативный вариант (с ответом)
server {
listen 80 default\_server;
server\_name \_;
return 403;
}
Возвращает 403 Forbidden вместо закрытия соединения.
Проверка default server
Запрос по IP должен вернуть 444 или 403
curl -H "Host: unknown-domain.com" http://YOUR\_SERVER\_IP
👉 Запросы без домена или с неизвестными доменами → сразу отклоняются.
Меньше логов, меньше шума, меньше попыток атак.
Логи: разделяй всегда
Никогда не используй общие логи для всех сайтов. Каждый сайт — свои логи.
Базовая настройка
server {
listen 80;
server\_name site1.ru;
root /var/www/site1.ru/public;
access\_log /var/www/site1.ru/logs/access.log;
error\_log /var/www/site1.ru/logs/error.log;
}
Расширенная настройка с уровнями
server {
listen 80;
server\_name site1.ru;
root /var/www/site1.ru/public;
Access log с форматом
access\_log /var/www/site1.ru/logs/access.log;
Error log с уровнем (debug, info, notice, warn, error, crit)
error\_log /var/www/site1.ru/logs/error.log warn;
}
Отключение логов для статики
server {
listen 80;
server\_name site1.ru;
root /var/www/site1.ru/public;
access\_log /var/www/site1.ru/logs/access.log;
error\_log /var/www/site1.ru/logs/error.log;
location / {
try\_files $uri $uri/ =404;
}
Статика без логов (экономия места)
location ~\* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
expires 30d;
access\_log off;
}
}
Создание директорий для логов
Создаём директорию для логов
mkdir -p /var/www/site1.ru/logs
Устанавливаем права (nginx должен писать)
chown -R www-data:www-data /var/www/site1.ru/logs
chmod 755 /var/www/site1.ru/logs
👉 Потом ты скажешь себе спасибо, когда нужно будет найти ошибку конкретного сайта.
Типовые ошибки при работе с virtual hosts
❌ Два сайта с одинаковым server_name
Конфиг 1
server {
server\_name example.com;
}
Конфиг 2
server {
server\_name example.com; # КОНФЛИКТ!
}
Решение: каждый домен — один server_name.
❌ Забыл reload после изменения конфига
Изменил конфиг, но забыл перезагрузить
vim /etc/nginx/sites-available/site.conf
Изменения не применятся!
Решение: всегда делай nginx -t && systemctl reload nginx.
❌ Конфиг не подключён через include
В nginx.conf нет include
http {
sites-enabled не подключён!
}
Решение: проверь nginx.conf, должен быть:
http {
include /etc/nginx/sites-enabled/\*;
}
❌ Неправильный root
server {
root /var/www/site.ru; # Без /public
Файлы доступны напрямую, включая .env, config.php и т.д.
}
Решение: всегда указывай root на /public директорию.
❌ Нет default_server
Первый блок становится дефолтным автоматически
server {
server\_name site1.ru; # Станет default для всех запросов
}
Решение: явно укажи default_server для мусорного блока.
❌ Неправильные права на файлы
Файлы недоступны для чтения
chmod 000 /var/www/site.ru/public/index.html
Nginx вернёт 403
Решение: проверь права:
chown -R www-data:www-data /var/www/site.ru
chmod -R 755 /var/www/site.ru
chmod -R 644 /var/www/site.ru/public/\*
❌ Опечатка в server_name
server {
server\_name mysite.ru; # Опечатка: должно быть mysite.com
}
Решение: всегда проверяй домен в server_name.
Мини-чеклист перед запуском сайта
Перед тем как считать сайт готовым, проверь:
- [] Конфиг лежит в нужной директории (sites-available для Ubuntu/Debian, conf.d для CentOS/Rocky)
- [] Для Ubuntu/Debian: создан симлинк в sites-enabled
- [] server_name совпадает с реальным доменом
- [] root указывает на /public директорию
- [] Логи раздельные для каждого сайта
- [] Директории для логов созданы и имеют правильные права
- [] nginx -t проходит без ошибок
- [] systemctl reload nginx выполнен успешно
- [] Сайт открывается по домену
- [] Default server настроен для защиты от мусорных запросов
Команды для проверки
Проверка конфигурации
nginx -t
Проверка статуса
systemctl status nginx
Просмотр активных конфигов (Ubuntu/Debian)
ls -la /etc/nginx/sites-enabled/
Просмотр всех конфигов (CentOS/Rocky)
ls -la /etc/nginx/conf.d/
Проверка, какие server блоки активны
nginx -T 2>/dev/null | grep -A 5 "server\_name"
Что будет в части 3
Следующий шаг — PHP-FPM и связка с Nginx:
- как Nginx передаёт PHP-запросы;
- сокет vs TCP;
- типовые ошибки 502 / 504;
- структура под PHP-проекты (Bitrix, Laravel, WordPress).
Итог
Virtual hosts (server blocks) — это основа масштабируемости Nginx.
Если сразу выстроить правильную структуру:
- сайты не мешают друг другу;
- конфиги читаются и легко поддерживаются;
- логи разделены, легко найти проблему;
- сервер не превращается в помойку;
- безопасность выше (правильный root, default server).
Это тот фундамент, который экономит десятки часов в будущем и избавляет от головной боли при масштабировании.
Полезные материалы
Практические сниппеты для работы с virtual hosts:
- Несколько virtual hosts на одном сервере — настройка нескольких сайтов для Ubuntu/Debian и CentOS/Rocky
- Default server для защиты от мусорных запросов — настройка default_server для отклонения запросов по IP
- Раздельные логи для каждого virtual host — настройка отдельных access_log и error_log для каждого сайта
- Правильная структура каталогов для нескольких сайтов — организация директорий public, logs, releases
Официальная документация Nginx:
- Server names — как работает server_name
- How nginx processes a request — алгоритм выбора server block
- Configuring NGINX as a Web Server — официальное руководство
Top comments (0)