Боль, которую мы лечим в этом уроке: ты добавил второй сайт, перезагрузил конфиг, а он почему-то отдаёт контент первого. Или ты убрал www, а половина ссылок отвалилась. Или поставил http2 on старым способом и получил [warn] при каждом nginx -t. Разберём механику до винтика, чтобы ты раз и навсегда понял, как nginx несколько сайтов разруливает по Host и listen.
Что такое server-блок и зачем он нужен
Базовая единица в Nginx, отвечающая за один логический сайт - это nginx server блок. Внутри http { ... } ты можешь объявить сколько угодно блоков server { ... }, и каждый из них - отдельный виртуальный хост. Минимальный пример на два сайта:
Код: Выделить всё
http {
server {
listen 80;
server_name shop.example.com;
root /var/www/shop;
}
server {
listen 80;
server_name blog.example.com;
root /var/www/blog;
}
}

Директива listen: порт, IP, default_server и флаги
Директива nginx listen говорит, на каком сокете слушать. Полная форма умеет куда больше, чем просто номер порта:
Код: Выделить всё
server {
listen 80;
listen [::]:80;
listen 443 ssl default_server reuseport;
server_name example.com;
}
Флаги после порта:
- default_server - этот блок становится дефолтным для данной пары адрес:порт. О нём чуть ниже отдельно, это важная штука.
- ssl - на этом сокете терминируется TLS. Без него ssl_certificate работать не будет.
- reuseport - включает опцию сокета SO_REUSEPORT: ядро создаёт отдельную слушающую очередь на каждый воркер и само балансирует входящие соединения между ними. Снимает локовую толкотню на accept под высокой нагрузкой. Указывается ОДИН раз на пару адрес:порт, не в каждом server.
Код: Выделить всё
nginx: [warn] the "listen ... http2" directive is deprecated, use the "http2" directive instead
Код: Выделить всё
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
}
server_name: точное имя, wildcard, regex, пустое и подчёркивание
Директива nginx server_name задаёт, на какие значения заголовка Host реагирует блок. Форм несколько, и Nginx обрабатывает их с разным приоритетом:
Код: Выделить всё
server { server_name example.com www.example.com; } # точные имена
server { server_name *.example.com; } # wildcard слева
server { server_name mail.*; } # wildcard справа
server { server_name ~^www\d+\.example\.com$; } # regex (тильда)
server { server_name ""; } # пустой Host
server { server_name _; } # "невалидное имя"
Особые случаи. server_name "" (пустая строка) ловит запросы вообще без заголовка Host - такие шлют кривые боты и старые HTTP/1.0 клиенты. server_name _ - это НЕ wildcard и не "любой хост". Подчёркивание - просто синтаксически невалидное доменное имя, которое гарантированно никогда не совпадёт ни с одним реальным Host. Его используют как заглушку для дефолтного блока-ловушки, чтобы тот не перехватывал чужие имена случайно. Запомни: _ ничего не матчит сам по себе, он ловит трафик только за счёт связки с default_server, а не за счёт имени.
Алгоритм выбора server: как Nginx находит нужный виртуальный хост
Это ядро урока. Когда приходит запрос, Nginx делает строго по шагам:
- Шаг 1. Берёт IP и порт, на которые пришло соединение, и заголовок Host из запроса.
- Шаг 2. Отбирает все server-блоки, у которых listen совпадает с этой парой адрес:порт. Точное совпадение адреса (listen 1.2.3.4:80) приоритетнее, чем wildcard-адрес (listen 80, то есть *:80).
- Шаг 3. Среди отобранных ищет server_name, совпадающий с Host, в таком порядке приоритета: точное имя, затем самый длинный wildcard, начинающийся со звёздочки (*.example.com), затем самый длинный wildcard, заканчивающийся звёздочкой (mail.*), затем первый по порядку regex, который сматчился.
- Шаг 4. Если по имени ничего не нашлось - запрос уходит в default_server для этой пары адрес:порт.
Проверим логику на конкретном наборе:
Код: Выделить всё
server { listen 80 default_server; server_name _; return 444; }
server { listen 80; server_name shop.example.com; root /var/www/shop; }
server { listen 80; server_name *.example.com; root /var/www/wild; }
server { listen 80; server_name ~^(?<sub>.+)\.example\.com$; root /var/www/re; }
SNI: как выбирается server для HTTPS
С HTTPS есть тонкость. Заголовок Host лежит ВНУТРИ зашифрованного запроса, а сертификат сервер должен предъявить ДО расшифровки, на этапе TLS-хендшейка. Откуда тогда Nginx знает, чей сертификат показать?
Ответ - SNI (Server Name Indication). Клиент в самом начале хендшейка, в открытом виде, в ClientHello кладёт имя хоста, к которому идёт. Nginx читает SNI, по нему выбирает server-блок и его ssl_certificate, и только потом расшифровывает запрос. Поэтому несколько HTTPS-сайтов с разными сертификатами на одном IP - это норма с тех пор, как SNI стал повсеместным. Дальше, после расшифровки, Nginx ещё раз сверяет уже настоящий Host из запроса и выбирает финальный server-блок - и если SNI и Host указывают на разные виртуальные хосты, решает именно Host. Практический вывод: для HTTPS тебе нужен валидный сертификат в каждом server-блоке (или один с несколькими SAN/wildcard), а дефолтный HTTPS-сервер выбирается по тем же правилам default_server, что и для HTTP.
Практика: канонизация хоста и редирект www <-> без www
Один сайт почти всегда доступен по двум адресам: example.com и www.example.com. Для SEO и для здравого смысла нужно выбрать один канонический и редиректить на него вечным 301. Делается это отдельным маленьким server-блоком, как в эталонном проекте (https_server.conf там добавляет отдельный server www->non-www 301):
Код: Выделить всё
# Канонический сайт - без www
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
root /var/www/example;
}
# Блок-редиректор: ловит www и гонит на голый домен
server {
listen 443 ssl;
http2 on;
server_name www.example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
return 301 https://example.com$request_uri;
}
Аналогично заворачивают весь HTTP на HTTPS - ещё одним блоком на listen 80, отдающим return 301 https://$host$request_uri.
Реальная структура http/https серверов и именованные @error-локации
В боевом проекте конфиг режут на файлы: default.conf подключает upstream и http_server, отдельно лежит https_server. В эталоне http_server.conf держит listen 80 default_server плюс IPv6, structured-логи и аккуратную обработку ошибок через ИМЕНОВАННЫЕ локации:
Код: Выделить всё
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
error_page 404 = @error404;
error_page 403 = @error403;
error_page 500 502 503 504 = @error50x;
location @error404 { internal; root /var/www/errors; rewrite ^ /404.html break; }
location @error403 { internal; root /var/www/errors; rewrite ^ /403.html break; }
location @error50x { internal; root /var/www/errors; rewrite ^ /50x.html break; }
location / {
root /var/www/html;
try_files $uri $uri/ =404;
}
}
Типичные грабли
- Не тот сайт по умолчанию. Нет явного default_server - дефолтным молча становится первый server в порядке загрузки файлов (а файлы из conf.d грузятся по алфавиту). Перетасовал include - сменился дефолт. Лечится явным default_server.
- Конфликт listen ... default_server. Помечать default_server можно ТОЛЬКО ОДИН блок на пару адрес:порт. Два таких на 80-м - nginx -t падает с "a duplicate default server". Это не баг, а защита от неоднозначности.
- http2 как флаг listen. На nginx 1.25.1 и новее это [warn], а не ошибка, поэтому многие не замечают годами. Перенеси на http2 on в server-блок.
- Забыл IPv6. listen 80 без listen [::]:80 - и IPv6-клиенты получают connection refused, хотя сайт "работает". Дублируй listen для [::] или используй один сокет, если система это позволяет.
- server_name _ приняли за wildcard. Оно само НЕ ловит ничего. Без default_server такой блок просто никогда не сработает.
- Дубли server_name. Если два блока на одном listen объявляют один и тот же домен, nginx -t выдаст "conflicting server name ... ignored" и второй молча выкинет. Проверяй nginx -t.
- Редирект www без сертификата на www. TLS-хендшейк случается раньше return 301, и клиент видит ошибку сертификата вместо твоего редиректа.
Повтори руками по шагам - 10 минут.
- Шаг 1. Создай каталоги и индексы: mkdir -p /var/www/{a,b,errors}, положи в /var/www/a/index.html строку "SITE A", в /var/www/b/index.html строку "SITE B".
- Шаг 2. Заведи /etc/nginx/conf.d/lab.conf с тремя блоками:
Код: Выделить всё
server {
listen 8080 default_server;
server_name _;
return 444;
}
server {
listen 8080;
server_name a.local;
root /var/www/a;
index index.html;
}
server {
listen 8080;
server_name b.local;
root /var/www/b;
index index.html;
}
- Шаг 3. Проверь синтаксис и перечитай конфиг: nginx -t затем nginx -s reload.
- Шаг 4. Постучись с правильным Host и убедись, что прилетает нужный сайт:
Код: Выделить всё
curl -s -H "Host: a.local" http://127.0.0.1:8080/ # -> SITE A
curl -s -H "Host: b.local" http://127.0.0.1:8080/ # -> SITE B
curl -si -H "Host: ghost.local" http://127.0.0.1:8080/ # -> пустой ответ, соединение оборвано (444)
- Шаг 5. Эксперимент: убери default_server из первого блока, перезагрузи, снова стукнись с Host: ghost.local. Теперь "ничейный" запрос попадёт в ПЕРВЫЙ блок по порядку (return 444 всё ещё там - значит соединение рвётся, но уже потому что блок первый, а не потому что дефолтный). Поменяй блоки местами - и поведение изменится. Так ты руками увидишь, как работает выбор дефолта.
- 1. По каким двум вещам Nginx в первую очередь отбирает кандидатов на server-блок ещё до сравнения server_name?
- 2. Чем server_name _ отличается от server_name *.example.com и почему _ сам по себе ничего не ловит?
- 3. Какой правильный синтаксис включения HTTP/2 в 2026 году и почему listen ... http2 даёт [warn]?
- 4. Как из заголовка Host (он зашифрован) Nginx понимает, чей TLS-сертификат предъявить на HTTPS, и что решает финальный выбор server - SNI или Host?
Виртуальный хост в Nginx - это server-блок, выбираемый по паре (listen-адрес:порт) и заголовку Host через server_name. Сначала фильтр по listen, потом матч по имени с приоритетом точное > wildcard > regex, а всё непойманное идёт в default_server (явный или первый по порядку). HTTPS добавляет SNI, чтобы выбрать сертификат до расшифровки. www-канонизацию делаем отдельным server-блоком с return 301, а не через if. И главное на 2026: http2 on - отдельной директивой, флаг listen http2 мёртв. Дальше будем разбирать location и маршрутизацию внутри уже выбранного хоста.