DEV Community

Андрей Викулов (VProger)
Андрей Викулов (VProger)

Posted on • Originally published at viku-lov.ru on

Nginx: virtual hosts и структура сайтов — как обслуживать несколько проектов правильно

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 выбирает нужный сайт

Алгоритм простой:

  1. Клиент подключается к IP и порту (80 или 443)
  2. В HTTP-заголовке передаётся Host
  3. Nginx ищет server_name, который совпадает
  4. Если не нашёл — берёт первый 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;

}

}

Enter fullscreen mode Exit fullscreen mode

Что здесь происходит

  • 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;

}

}

Enter fullscreen mode Exit fullscreen mode

Где хранить конфиги сайтов (важно)

Ubuntu / Debian (рекомендуемый подход)


/etc/nginx/

├── sites-available/

│ ├── site1.conf

│ └── site2.conf

└── sites-enabled/

├── site1.conf -> ../sites-available/site1.conf

└── site2.conf -> ../sites-available/site2.conf

Enter fullscreen mode Exit fullscreen mode

Активация сайта:


Создаём симлинк

ln -s /etc/nginx/sites-available/site1.conf /etc/nginx/sites-enabled/

Проверяем конфигурацию

nginx -t

Если проверка прошла успешно, перезагружаем

systemctl reload nginx

Enter fullscreen mode Exit fullscreen mode

Пример полного конфига для 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;

}

}

Enter fullscreen mode Exit fullscreen mode

Активируем:


ln -s /etc/nginx/sites-available/mysite.conf /etc/nginx/sites-enabled/

nginx -t && systemctl reload nginx

Enter fullscreen mode Exit fullscreen mode

Отключение сайта


Удаляем симлинк

rm /etc/nginx/sites-enabled/mysite.conf

Проверяем и перезагружаем

nginx -t && systemctl reload nginx

Enter fullscreen mode Exit fullscreen mode

CentOS / Alma / Rocky


/etc/nginx/conf.d/

├── site1.conf

├── site2.conf

└── api.site3.conf

Enter fullscreen mode Exit fullscreen mode

Тут всё проще — файл есть → сайт активен. Симлинки не нужны.

Пример конфига для 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;

}

}

Enter fullscreen mode Exit fullscreen mode

Проверяем и перезагружаем:


nginx -t && systemctl reload nginx

Enter fullscreen mode Exit fullscreen mode

Отключение сайта в 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

Enter fullscreen mode Exit fullscreen mode

Правильная структура каталогов сайтов

❌ Плохо (часто вижу в проде)


/var/www/

├── site1

├── site2

├── site3

Enter fullscreen mode Exit fullscreen mode

Без логики, всё вперемешку.


✅ Хорошо (чётко и масштабируемо)


/var/www/

├── site1.ru/

│ ├── public/

│ ├── logs/

│ └── releases/ (если есть деплой)

├── site2.ru/

│ ├── public/

│ └── logs/

Enter fullscreen mode Exit fullscreen mode

Почему 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;

}

Enter fullscreen mode Exit fullscreen mode

Как работает:

  • 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;

}

}

Enter fullscreen mode Exit fullscreen mode

alias (осторожно)

Директива alias заменяет путь из location:


location /media/ {

alias /data/uploads/;

}

Enter fullscreen mode Exit fullscreen mode

Как работает:

  • URL: /media/file.jpg
  • Файл на диске: /data/uploads/file.jpg
  • Nginx берёт alias + URI (без /media/)

❌ Частая ошибка с alias


НЕПРАВИЛЬНО

location /media {

alias /data/uploads/;

}

Enter fullscreen mode Exit fullscreen mode

При запросе /media/file.jpg Nginx будет искать /data/uploads//file.jpg (двойной слэш).

✅ Правильно


location /media/ {

alias /data/uploads/;

}

Enter fullscreen mode Exit fullscreen mode

Или:


location /media {

alias /data/uploads;

}

Enter fullscreen mode Exit fullscreen mode

👉 Правило: если в location есть завершающий слэш, он должен быть и в alias.

Когда использовать alias

Используй alias, когда нужно отдать файлы из другой директории, не связанной со структурой сайта:


Статические файлы из отдельного хранилища

location /uploads/ {

alias /mnt/storage/uploads/;

}

Или файлы из другого проекта

location /legacy/ {

alias /var/www/old-site/public/;

}

Enter fullscreen mode Exit fullscreen mode

Несколько сайтов на одном 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;

}

}

Enter fullscreen mode Exit fullscreen mode

Как это работает

  1. Клиент отправляет запрос на IP сервера
  2. В HTTP-заголовке Host передаётся домен (например, Host: site1.ru)
  3. Nginx ищет server_name, который совпадает с Host
  4. Если совпадение найдено — применяется конфигурация этого блока
  5. Если не найдено — используется 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;

}

Enter fullscreen mode Exit fullscreen mode

Что здесь происходит:

  • listen 80 default_server — этот блок становится дефолтным для порта 80
  • server_name _ — специальное значение, означает "любой домен, который не совпал"
  • return 444 — закрывает соединение без ответа (экономит трафик)

Альтернативный вариант (с ответом)


server {

listen 80 default\_server;

server\_name \_;

return 403;

}

Enter fullscreen mode Exit fullscreen mode

Возвращает 403 Forbidden вместо закрытия соединения.

Проверка default server


Запрос по IP должен вернуть 444 или 403

curl -H "Host: unknown-domain.com" http://YOUR\_SERVER\_IP

Enter fullscreen mode Exit fullscreen mode

👉 Запросы без домена или с неизвестными доменами → сразу отклоняются.

Меньше логов, меньше шума, меньше попыток атак.


Логи: разделяй всегда

Никогда не используй общие логи для всех сайтов. Каждый сайт — свои логи.

Базовая настройка


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;

}

Enter fullscreen mode Exit fullscreen mode

Расширенная настройка с уровнями


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;

}

Enter fullscreen mode Exit fullscreen mode

Отключение логов для статики


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;

}

}

Enter fullscreen mode Exit fullscreen mode

Создание директорий для логов


Создаём директорию для логов

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

Enter fullscreen mode Exit fullscreen mode

👉 Потом ты скажешь себе спасибо, когда нужно будет найти ошибку конкретного сайта.


Типовые ошибки при работе с virtual hosts

❌ Два сайта с одинаковым server_name


Конфиг 1

server {

server\_name example.com;

}

Конфиг 2

server {

server\_name example.com; # КОНФЛИКТ!

}

Enter fullscreen mode Exit fullscreen mode

Решение: каждый домен — один server_name.

❌ Забыл reload после изменения конфига


Изменил конфиг, но забыл перезагрузить

vim /etc/nginx/sites-available/site.conf

Изменения не применятся!

Enter fullscreen mode Exit fullscreen mode

Решение: всегда делай nginx -t && systemctl reload nginx.

❌ Конфиг не подключён через include


В nginx.conf нет include

http {

sites-enabled не подключён!

}

Enter fullscreen mode Exit fullscreen mode

Решение: проверь nginx.conf, должен быть:


http {

include /etc/nginx/sites-enabled/\*;

}

Enter fullscreen mode Exit fullscreen mode

❌ Неправильный root


server {

root /var/www/site.ru; # Без /public

Файлы доступны напрямую, включая .env, config.php и т.д.

}

Enter fullscreen mode Exit fullscreen mode

Решение: всегда указывай root на /public директорию.

❌ Нет default_server


Первый блок становится дефолтным автоматически

server {

server\_name site1.ru; # Станет default для всех запросов

}

Enter fullscreen mode Exit fullscreen mode

Решение: явно укажи default_server для мусорного блока.

❌ Неправильные права на файлы


Файлы недоступны для чтения

chmod 000 /var/www/site.ru/public/index.html

Nginx вернёт 403

Enter fullscreen mode Exit fullscreen mode

Решение: проверь права:


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/\*

Enter fullscreen mode Exit fullscreen mode

❌ Опечатка в server_name


server {

server\_name mysite.ru; # Опечатка: должно быть mysite.com

}

Enter fullscreen mode Exit fullscreen mode

Решение: всегда проверяй домен в 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"

Enter fullscreen mode Exit fullscreen mode

Что будет в части 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:

Read more on viku-lov.ru

Top comments (0)