Один сервер Nginx направляет запросы трёх доменов в отдельные каталоги сайтов
DevOps

Виртуальные хосты Nginx для нескольких доменов

Настраиваем несколько сайтов в Nginx: отдельные server-блоки, default_server, server_name, тест через Host и --resolve, безопасное включение и откат.

Содержание

Несколько доменов могут указывать на один IP-адрес. Nginx различает их по имени, которое передаёт клиент, и для каждого выбирает свой блок server. Так сайты получают отдельные каталоги, журналы и правила, хотя используют один веб-сервер.

При настройке важно явно определить поведение для неизвестного имени. Иначе запрос, не совпавший ни с одним server_name, может показать первый настроенный сайт и раскрыть чужой проект на неожиданном домене.

Подготовьте карту доменов

До редактирования запишите для каждого сайта:

Домен Каталог Тип HTTPS
example.com /var/www/example/current статика отдельный сертификат
docs.example.com /var/www/docs/current статика отдельный сертификат

Не копируйте эти имена как настоящую настройку. Замените домены и пути, проверьте DNS и владельцев каталогов.

Разделяйте production-файлы проектов. Общий корень с произвольными подпапками усложняет права, deploy и удаление. Веб-серверу обычно достаточно чтения, а пользователь публикации владеет релизами.

Создайте безопасный default server

Создайте отдельный блок, который принимает запросы с неизвестным Host на порту 80 и возвращает обычный 404. Роль сервера по умолчанию задаёт параметр default_server в listen:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 404;
}

default_server относится к адресу и порту. Строка server_name _ не создаёт магического wildcard, а служит заведомо неподходящим обычным именем. Если IPv6 на сервере не настроен, не добавляйте listen [::] без проверки.

В некоторых конфигурациях неизвестные запросы закрывают нестандартным кодом Nginx. Обычный 404 проще для обучения и наблюдения. Выбор зависит от политики сервера.

Настройте первый домен

В отдельном файле сайта укажите его имена, каталог опубликованных файлов и журналы. Следующий пример обслуживает example.com и www.example.com как один проект:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/example/current;
    index index.html;

    access_log /var/log/nginx/example.access.log;
    error_log /var/log/nginx/example.error.log;

    location / {
        try_files $uri $uri/ =404;
    }
}

Точные имена перечислены явно. root указывает только на опубликованный релиз, не на репозиторий или домашний каталог. Проверка try_files не передаёт неизвестный URI другому приложению.

Перед reload убедитесь, что Nginx может пройти путь и прочитать файл:

namei -l /var/www/example/current/index.html
sudo -u www-data test -r /var/www/example/current/index.html

Имя worker-пользователя уточните по действующей конфигурации; оно не универсально.

Отделите второй сайт

Для docs.example.com создайте другой server-блок с собственным каталогом и журналами. Такое разделение позволяет обновлять и отключать проекты независимо:

server {
    listen 80;
    listen [::]:80;
    server_name docs.example.com;

    root /var/www/docs/current;
    index index.html;

    access_log /var/log/nginx/docs.access.log;
    error_log /var/log/nginx/docs.error.log;

    location / {
        try_files $uri $uri/ =404;
    }
}

Не смешивайте домены разных проектов в одном server_name, если у них разные релизы, политика TLS или журналы. Отдельный блок упрощает откат и диагностику.

На Debian/Ubuntu файл часто создают в sites-available, а в sites-enabled помещают символическую ссылку. Это соглашение пакета, а не обязательное правило самого Nginx:

sudo ln -s /etc/nginx/sites-available/example.conf /etc/nginx/sites-enabled/example.conf

Сначала убедитесь, что путь включения действительно подключён из nginx.conf, а ссылка с таким именем не существует.

Проверьте конфликты в полном дереве

sudo nginx -t
sudo nginx -T 2>&1 | less

Найдите все listen, server_name и default_server. Предупреждение о конфликтующем имени означает, что часть настройки может игнорироваться. Не пытайтесь решить конфликт перестановкой файлов: одно имя должно иметь понятного владельца на конкретном адресе и порту.

Избегайте широких регулярных server_name, пока точных имён достаточно. Wildcard и regex имеют собственный приоритет и могут принять лишний домен.

Тестируйте до изменения DNS

Ниже используется зарезервированный для документации адрес 203.0.113.10; замените его фактическим IP проверяемого сервера.

Для HTTP:

curl -i -H 'Host: example.com' http://203.0.113.10/
curl -i -H 'Host: docs.example.com' http://203.0.113.10/
curl -i -H 'Host: unknown.invalid' http://203.0.113.10/

Третий запрос должен показать поведение default server, а два первых — разные маркеры сайтов. Не используйте только одинаковые шаблоны страниц: разместите в тестовых index.html различимый текст.

Для HTTPS корректно передайте и SNI, и Host:

curl -i --resolve example.com:443:203.0.113.10 https://example.com/

--resolve меняет разрешение имени для этого запроса. Публичный DNS остаётся прежним. Сертификат должен соответствовать домену; постоянный -k скрыл бы важную ошибку.

Добавьте HTTPS без дублирования логики

Сначала получите и проверьте сертификат штатным способом вашего центра сертификации. Не придумывайте путь к ключу и не копируйте чужой конфиг. В HTTPS server указываются listen 443 ssl, точные имена, сертификат и ключ.

HTTP-блок обычно либо обслуживает проверку центра сертификации, либо перенаправляет известные домены на HTTPS:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

Такой редирект канонизирует www на основной домен. Сначала проверьте, что это соответствует выбранной структуре URL и сертификат уже работает. Не добавляйте HSTS до устойчивой работы HTTPS на всех нужных поддоменах.

Перед добавлением нескольких виртуальных хостов настройте один домен, TLS и внешний запрос по базовой схеме Nginx.

Разделите журналы и наблюдение

Отдельный access log помогает понять трафик конкретного домена. Error log может быть общим или отдельным, но его расположение должно быть предсказуемым. После reload выполните:

systemctl status nginx --no-pager
sudo tail -n 30 /var/log/nginx/example.error.log

Затем проверьте главную, вложенный URL, отсутствующую страницу и статический ресурс. Код 200 у одного адреса ещё не доказывает, что выбран правильный root.

Откат

Если новый сайт мешает существующему, отключите только его include или ссылку, верните предыдущий default server при необходимости, затем выполните:

sudo nginx -t
sudo systemctl reload nginx

Не удаляйте каталог сайта и сертификат одновременно с отключением: сохраните артефакты до подтверждения отката. Повторите запросы ко всем прежним доменам и неизвестному Host.

После выбора виртуального хоста запрос обрабатывают правила location, root, alias и try_files. Если Nginx выбирает неожиданный блок, восстановите весь путь HTTP-запроса по журналу и активной конфигурации.

Первоисточники

Рекламное местоВаша компания здесьРазместить рекламу

Самопроверка

Проверьте, что материал усвоен

Ответьте на все вопросы. Результат сохранится только в этом браузере и будет учтён в статистике прочитанных материалов.

01Что определяет сервер по умолчанию для порта 80?
02Как проверить новый HTTPS-хост до изменения публичного DNS?
03Что безопаснее для каталогов двух независимых сайтов?

Разбираем коротко

Частые вопросы

Сколько сайтов можно обслуживать одним Nginx?

Жёсткого универсального числа нет: один экземпляр может обслуживать много виртуальных серверов, если хватает ресурсов и конфигурация остаётся управляемой. Ограничения определяют трафик, TLS, журналы, файлы и приложения.

Нужно ли каждому домену назначать отдельный IP-адрес?

Обычно нет. Несколько доменов могут указывать на один IP, а Nginx выбирает server-блок по имени запроса. Отдельный адрес нужен только для специальных сетевых или организационных требований.

Почему новый домен показывает уже существующий сайт?

Чаще всего запрос попал в default_server, потому что нужный server-блок не подключён, server_name не совпал или DNS ведёт на другой сервер. Проверьте nginx -T и запрос с явным Host или --resolve.