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

Один сервер — десятки сайтов. Nginx разбирает, кому какой запрос, по заголовку Host, как почтальон по адресу на конверте.
«server_name — это имя на конверте. listen — это дверь, в которую стучатся. Nginx сопоставляет одно с другим.»

На одной машине обычно крутится не один сайт. Nginx разделяет их с помощью виртуальных хостов — блоков server. Каждый блок описывает один сайт: на каком порту слушать и на какое доменное имя отвечать.

Два ключевых параметра

  • listen — какой порт (и адрес) слушать: listen 80; или listen 443 ssl;.
  • server_name — на какие доменные имена отвечает этот блок: server_name example.com www.example.com;.
http {
    server {
        listen 80;
        server_name site-a.com;
        root /var/www/site-a;
    }

    server {
        listen 80;
        server_name site-b.com;
        root /var/www/site-b;
    }
}

Оба слушают порт 80, но отвечают разным доменам. Браузер в каждом HTTP-запросе шлёт заголовок Host: site-a.com, и Nginx по нему выбирает нужный server-блок.

Как Nginx выбирает блок

   Запрос: GET / Host: site-b.com
                  |
                  v
   +-------------------------------+
   |  все server с listen 80       |
   |                               |
   |  server_name site-a.com  ---- нет
   |  server_name site-b.com  ---- ДА -> этот блок
   +-------------------------------+

Как работает под капотом

Сначала Nginx фильтрует блоки по listen (адрес:порт). Если на этот порт подходит несколько server, он смотрит server_name и сопоставляет с заголовком Host. Порядок сопоставления: точное имя, затем маска вида *.example.com, затем маска вида www.example.*, затем регулярные выражения. Если ничего не совпало, используется default_server — блок, помеченный listen 80 default_server;, а если такого нет — первый по порядку. Поэтому «левые» запросы по IP без known-домена попадают именно в default_server.

Частые ошибки

  • Нет default_server для «мусорных» запросов. Боты ходят по голому IP с любым Host; без явного default_server они попадут в первый попавшийся сайт. Заведи «заглушку», отдающую 444.
  • Опечатка в server_name. Тогда запрос молча уедет в default_server, и ты будешь видеть «не тот» сайт.
  • Забыть www или наоборот. Перечисли все варианты или сделай редирект с одного на другой.

Best practices

  • Заведи явный default_server со return 444; для неизвестных Host — это закрывает целый класс проблем и сканеров.
  • Один сайт = один файл конфигурации (см. предыдущий урок).
  • Канонизируй домен: выбери example.com или www.example.com и редиректь второй на первый для SEO.

Канонизация домена и редиректы

Один из самых частых сценариев виртуальных хостов — приведение всех вариантов адреса к одному каноническому. Сайт доступен как example.com, www.example.com, по http и по https — это четыре разных URL для одной страницы, что плохо и для пользователей, и для поисковиков (дублирование контента размывает SEO). Решение: выбрать одну каноническую форму и редиректить на неё все остальные отдельными server-блоками с return 301.

Вот типичная связка: один server слушает порт 80 и редиректит на https; другой ловит www-вариант и редиректит на основной домен; и только третий, основной, реально обслуживает сайт. Такой подход чище, чем нагромождение if внутри одного блока — Nginx-сообщество даже придумало присказку «if is evil», потому что if в location ведёт себя неинтуитивно. Отдельные server-блоки с return работают быстрее и понятнее. И не забывай про default_server со return 444; (особый код Nginx, который просто закрывает соединение без ответа) — он отсекает ботов, стучащихся по голому IP с произвольным Host. Эти несколько блоков-«стражей» решают целый класс типовых проблем продакшена раз и навсегда.

Итоги

Виртуальные хосты — это блоки server, различаемые по listen (порт) и server_name (домен). Nginx выбирает блок по заголовку Host, а непонятные запросы отправляет в default_server. Это позволяет держать множество сайтов на одной машине. Дальше — глубокое погружение в раздачу статики и магию директивы location.

Проверьте себя
1. По какому признаку Nginx выбирает нужный server-блок среди слушающих один порт?
AПо IP клиента
BПо заголовку Host, сопоставляя его с server_name
CСлучайно
DПо размеру запроса
2. Куда попадёт запрос с неизвестным Host, не совпавшим ни с одним server_name?
AВернётся ошибка 500
BВ блок default_server (или, если он не задан, в первый по порядку)
CБудет отброшен на уровне ядра
DВ каждый блок по очереди