Прямой ответ
Несколько сайтов на одном IP — стандартная схема: nginx выбирает виртуальный хост по заголовку Host (HTTP) и по SNI (TLS — имя домена передаётся в ClientHello до шифрования, иначе сервер не знает, какой сертификат показывать — механика TLS); блоки server { server_name a.example.com; ... } описывают сайты, а запрос без совпадения уходит в default_server (первый описанный, если явно не назначен — источник половины проблем «открывается чужой сайт»). Практическая конфигурация: на каждый домен — свой server-блок со своим сертификатом, общий catch-all default_server с заглушкой/444 (не отдавать контент первого сайта случайным запросам по IP — сканеры и старые DNS-записи приходят постоянно), nginx -t перед каждым reload. Типовые сбои мультидоменности разбираются одной командой диагностики: `curl -vkI --resolve имя:443:IP — показывает, какой сертификат и какой сервер отвечает, без правки hosts и ожидания DNS (DNS-основы).
Как nginx выбирает server-блок
Алгоритм (стоит знать — снимает «магию»): для HTTP — nginx берёт заголовок Host, ищет точное совпадение server_name, затем wildcard (*.example.com), затем регулярные; не нашёл — default_server порта (или первый server-блок, если default не назначен). Для HTTPS выбор происходит дважды: сначала SNI выбирает server-блок (и сертификат для рукопожатия — без SNI или при неизвестном имени — сертификат default_server'а), затем Host внутри расшифрованного запроса подтверждает выбор (при несовпадении — тот же default или строже). Следствия: апекс и www — два имени (server_name example.com www.example.com — оба в одном блоке, редирект на канонический); порты — отдельные default'ы (default_server для 443 и 80 назначаются раздельно); один сервер — много сертификатов нормально (каждый server-блок со своим ssl_certificate; ограничения «один IP — один сайт» не существует со времён SNI, единственные клиенты без SNI — реликты). И порядок блоков в конфиге не важен для матчинга (важен — только для выбора default при отсутствии явного указания).
Конфигурация: три сайта и правильная заглушка
Каркас: /etc/nginx/sites-available/ — по файлу на сайт (site-a.conf, site-b.conf...), симлинки в sites-enabled — включение/выключение без правок. Каждый файл: server { listen 443 ssl http2; server_name a.example.com; ssl_certificate /etc/letsencrypt/live/a.example.com/fullchain.pem; ssl_certificate_key .../privkey.pem; location / { proxy_pass } } + 80-блок с редиректом на https (базовая настройка прокси). Отдельно — catch-all default_server (первым или с явной меткой): server { listen 443 ssl default_server; server_name _; ssl_certificate .../default.pem; return 444; } — запросы по голому IP, несуществующим доменам и старым DNS-записям получают обрыв соединения (444 — nginx-специфика: закрыть без ответа), а не контент первого сайта. Почему это важно: интернет-сканеры стучат по IP постоянно, «случайный» сайт на default — утечка контента на чужие домены (домен клиента истёк, его подхватили — ваш сервер отдаёт ваш контент по чужому домену) и путаница в логах. Сертификаты — через certbot с --nginx или webroot (автопродление): конфиги новых сайтов certbot правит сам.
«Отдаётся не тот сайт»: диагностика за две команды
Симптом-классика: «при переходе на b.example.com иногда вижу сайт A». Диагностика: (1) curl -vkI --resolve b.example.com:443:IP — смотрим строку subject: CN=...сертификата: если CN=a.example.com — SNI не сматчил блок B (блок отсутствует/имя с опечаткой/не включён симлинк sites-enabled — проверитьnginx -T | grep -A2 server_name); если CN верный, а контент чужой — Host-матчинг внутри (proxy_pass ведёт не на тот порт приложения). (2) curl -I -H "Host: b.example.com" — проверка HTTP-ветки (какой server-блок отвечает по Host без TLS). Типовые находки: забытый симлинк в sites-enabled (конфиг написан, но не подключён — nginx -T показывает фактически загруженное, а не файлы на диске — главная отладочная команда); опечатка в server_name (точка в конце, лишний пробел); конфликт дублей server_name в двух файлах (nginx предупредит при nginx -t: conflicting server name — но продолжит работать с первым). После фикса: nginx -t && systemctl reload nginx и повторная проверка curl — сертификат и контент совпали с ожидаемыми.
Практический сценарий: переезд клиента на общий сервер
Задача: второй сайт переезжает на сервер, где уже живёт первый. Шаги: (1) DNS нового домена — A-запись на IP сервера (TTL снижен заранее — миграции); (2) server-блок b.example.com: прокси на 127.0.0.1:8002 (приложение второго сайта уже развёрнуто и слушает локально — firewall: наружу открыты только 80/443, порты приложений — localhost); (3) certbot --nginx -d b.example.com -d www.b.example.com — сертификат выпущен (DNS уже работает — валидация прошла), certbot добавил ssl-строки в блок; (4) проверка: curl -vkI --resolve ... — CN=b.example.com, контент B; запрос по IP — 444 от default-блока (проверка, что новый сайт не стал дефолтным: certbot иногда добавляет default_server — убрать, если так); (5) логи: access_log отдельным файлом на сайт (access_log /var/log/nginx/b.access.log — структура логов: аналитика и отладка per-site, не в общей каше); мониторинг — чек доступности обоих доменов. Сценарий отказа: через день клиент сообщает «иногда старый сайт» — диагностика по разделу выше: старый сервер клиента ещё жив и DNS у части резолверов старый — не проблема nginx, ожидание TTL; фикс — выключить старый сервер после схождения кешей.
FAQ
Нужен ли отдельный IP на каждый HTTPS-сайт? Нет: SNI (RFC 6066) решает это с 2000-х — один IP обслуживает произвольное число сертификатов; отдельный IP нужен лишь для клиентов без SNI (практически исчезли) или специфических требований (выделенный IP под домен у почтовых/платёжных политик).
Что показывать на запрос по IP без домена? 444 (закрыть) или минимальную заглушку «сайт обслуживается по доменным именам»; показ первого сайта — плохая практика (контент утекает на чужие домены, метрики/логи засоряются, SEO-дубли по IP).
HTTP/2 и HTTP/3 меняют выбор виртуального хоста?
Механика та же: HTTP/2 — SNI обязателен (без него соединение не установится), Host живёт в :authority; HTTP/3 (QUIC) — SNI в незашифрованном Initial-пакете. Для конфигурации nginx разница минимальна: listen 443 ssl http2 (и listen 443 quic для v3), server-блоки без изменений.
Как отладить выбор сервера без правки DNS?
curl --resolve имя:порт:IP — точечная подмена резолвинга на одну команду (лучше /etc/hosts: не забывается); openssl s_client -connect IP:443 -servername имя — проверка только TLS/SNI-ветки; nginx -T — весь фактически загруженный конфиг одним выводом.
Источники: документация nginx (request processing, HTTPS), RFC 6066 (SNI) — ссылки в шапке.