← Назад в блог

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

Практическое руководство по настройке virtual hosts (server blocks) в Nginx: несколько сайтов на одном сервере, правильная структура каталогов, разделение логов, default server. Вторая часть серии о настройке веб-сервера.

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

Требования

  • Базовое знание Linux/терминала
  • Доступ к серверу (VPS или локальный)
  • Прочитана первая часть серии про Nginx

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

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

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

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

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

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

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

Официальная документация Nginx:

Предыдущая часть: Nginx: база и фундамент — как устроен веб-сервер и с чего начинать
0 просмотров

Комментарии

Загрузка комментариев...
Пока нет комментариев. Будьте первым!