MAATRIX / Блог / Что такое SNI и как один IP держит сотню HTTPS-сайтов

Что такое SNI и как один IP держит сотню HTTPS-сайтов

MAATRIX

Если вы когда-нибудь настраивали несколько HTTPS-сайтов на одном VPS и всё просто заработало — за этим стоит механизм, который решает нетривиальную задачу: сервер должен предъявить клиенту сертификат ещё до того, как узнаёт, к какому домену тот вообще обращается. Разбираемся, как SNI разруливает это противоречие, что при этом остаётся видно посторонним и как это использовать при настройке виртуального хостинга.

Противоречие, которое решает SNI

HTTPS — это HTTP поверх TLS. Клиент сначала устанавливает TLS-соединение, и только внутри уже зашифрованного канала отправляет HTTP-запрос с заголовком Host: example.com, который и говорит серверу, какой сайт нужен. Проблема в том, что TLS-рукопожатие происходит раньше — сервер должен отправить сертификат ещё в процессе установления соединения, до того как клиент передал вообще что-либо о том, какой домен ему нужен.

Получается замкнутый круг: чтобы понять, какой сертификат отдавать, серверу нужно знать домен, а узнать домен он мог бы только из HTTP-запроса, который придёт уже после того, как TLS-соединение установлено и сертификат уже отправлен. Если на IP-адресе висит один сайт с одним сертификатом — проблемы нет, сервер всегда отдаёт один и тот же сертификат. Но как только на одном IP нужно разместить несколько HTTPS-доменов с разными сертификатами, встаёт вопрос: какой из них показать, если имя домена ещё неизвестно?

Про то, что вообще происходит в TLS до того, как передан первый байт полезных данных, подробнее в статье про TLS-рукопожатие — здесь остановимся именно на проблеме выбора сертификата.

Как жили до SNI: один IP — один сайт

Расширение SNI (Server Name Indication) описано в RFC 6066 и существует уже давно, но напоминание о временах до него полезно, потому что оно объясняет, почему до сих пор в старых мануалах встречаются формулировки вроде «для HTTPS нужен отдельный IP на сайт».

Без SNI единственный способ обслуживать несколько HTTPS-доменов с разными сертификатами — дать каждому домену собственный IP-адрес. Сервер слушает несколько IP на порту 443, и по тому, на какой именно адрес пришло соединение, определяет, какой сертификат отдавать — задолго до того, как клиент скажет что-то про домен. Это классическая IP-based виртуализация: домен привязан не к имени, а к адресу.

Схема рабочая, но дорогая и неудобная:

  • каждый новый HTTPS-сайт на shared-хостинге требовал отдельный IPv4-адрес, а свободных адресов становилось всё меньше;
  • на сервере росло число сетевых интерфейсов/алиасов, которые нужно было администрировать;
  • перенос сайта между серверами означал ещё и перенос или переиздание привязки IP.

Именно дефицит IPv4 и стал одной из практических причин, почему индустрия дружно перешла на SNI, как только браузеры и серверы его массово поддержали. HTTP по обычному 80-порту такой проблемы никогда не знал — виртуальный хостинг по имени домена (Host-заголовок) там работал с самого начала, потому что заголовок передаётся уже после установления TCP-соединения, а никакого шифрования, скрывающего его, не требовалось.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Что именно передаёт SNI и где это происходит

SNI — это расширение самого первого сообщения TLS-рукопожатия, ClientHello. Когда браузер инициирует TLS-соединение, он не просто присылает список поддерживаемых шифров и версию протокола — он добавляет в это же сообщение имя хоста, к которому обращается, в виде отдельного поля server_name.

Ключевой момент: ClientHello передаётся до согласования шифрования сессии, то есть в открытом виде. Это не баг, а необходимость — на этом этапе клиент и сервер ещё не договорились об общем ключе, шифровать нечем. Порядок обмена выглядит так:

  1. Клиент отправляет ClientHello, где среди прочего — расширение SNI с именем домена (например, example.com) в чистом тексте.
  2. Сервер читает SNI ещё до того, как выбрал сертификат, и на основе имени домена решает, какой именно сертификат и приватный ключ использовать для этого рукопожатия.
  3. Сервер отвечает ServerHello и присылает выбранный сертификат — уже строго тот, что выпущен на запрошенный домен.
  4. Стороны согласуют ключи сессии, и только после этого начинается зашифрованный обмен, внутри которого клиент отправит HTTP-запрос с заголовком Host.

С точки зрения сервера это будничная операция: у него есть таблица соответствий «имя домена → сертификат и ключ», и SNI — это просто индекс для поиска в этой таблице. Nginx, Apache, Caddy, HAProxy — все читают SNI на уровне TLS-терминации и выбирают виртуальный хост ещё до того, как декодируют HTTP.

Проверить это можно вручную:

openssl s_client -connect 203.0.113.10:443 -servername example.com | openssl x509 -noout -subject

Если поменять -servername на другой домен, размещённый на том же IP, вернётся другой сертификат — это и есть SNI в действии, безо всякого HTTP.

Как сервер выбирает виртуальный хост по SNI

На практике это выглядит как обычная конфигурация nginx с несколькими server{}-блоками на одном IP и разными server_name/ssl_certificate:

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    root /var/www/example.com;
}

server {
    listen 443 ssl;
    server_name shop.example.net;
    ssl_certificate     /etc/letsencrypt/live/shop.example.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/shop.example.net/privkey.pem;
    root /var/www/shop.example.net;
}

Nginx компилирует из всех server{}-блоков с TLS внутреннюю карту «SNI-имя → сертификат» и на этапе ClientHello сразу отдаёт нужный сертификат — HTTP-логика (location, proxy_pass и т.д.) подключается уже после того, как рукопожатие завершено. Технически сервер может обслуживать сколь угодно много доменов на одном IP — практический потолок определяется не протоколом, а ресурсами сервера и удобством администрирования, а не числом IP-адресов.

Нюанс: если пришёл клиент вообще без SNI (редкость, но встречается — некоторые старые библиотеки и часть корпоративных прокси/сканеров), nginx отдаёт сертификат первого подходящего по адресу и порту server{}-блока (или явно помеченного default_server). Если в этом блоке домен не совпадает с ожидаемым, браузер покажет предупреждение о несовпадении имени в сертификате — стоит держать на default_server либо самый приоритетный сайт, либо специально подготовленную заглушку.

Отдельно стоит развести SNI и wildcard-сертификаты: wildcard (*.example.com) закрывает поддомены одного домена одним сертификатом, но не помогает, если на IP живут разные домены (example.com и shop.example.net) — там всё равно нужны отдельные сертификаты, и именно SNI позволяет серверу выбрать нужный. Про то, как вообще устроен процесс выпуска сертификата и подтверждения владения доменом, — в статье про Let's Encrypt и проверку прав на домен.

Что видно посторонним: SNI и приватность

Раз имя домена в ClientHello передаётся в открытом виде, любой узел на пути пакета — провайдер, оператор Wi-Fi, транзитная сеть, DPI-система — видит, к какому домену вы обращаетесь, даже если сам HTTP-трафик внутри TLS-сессии зашифрован и недоступен. Это не значит, что HTTPS «дырявый» — содержимое запроса, cookies, тело страницы остаются защищены, но сам факт визита на конкретный домен виден.

Это принципиально отличается от заблуждения «раз сайт на HTTPS, значит он в принципе безопасен» — HTTPS защищает канал передачи, но не гарантирует ничего про содержимое или репутацию сайта, и не скрывает сам факт обращения к домену из-за открытого SNI. Подробнее о том, что HTTPS на самом деле даёт, а что нет, — в статье про миф «HTTPS значит сайт безопасный».

На практике открытый SNI используют для:

  • фильтрации трафика по доменам на уровне провайдера или корпоративного шлюза без расшифровки самого содержимого;
  • диагностики и биллинга у CDN и защитных прокси — по SNI решают, к какому бэкенду/тарифу относится соединение;
  • блокировок конкретных доменов провайдерами, не трогая остальной HTTPS-трафик на том же CDN.

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

Encrypted Client Hello: как скрыть даже имя домена

Encrypted Client Hello (ECH, ранее известный под именем ESNI как более ранняя версия идеи) — это развитие TLS, которое шифрует само расширение server_name вместе с остальными чувствительными полями ClientHello. Идея в том, чтобы клиент сначала получил публичный ключ сервера отдельным способом (обычно через DNS-запись HTTPS/SVCB, часто по DNS-over-HTTPS), зашифровал им внутренний, «настоящий» ClientHello с реальным именем домена, а наружу отправил внешний ClientHello с обезличенным именем — например, общим именем CDN вместо конкретного сайта клиента.

Важно понимать ограничения на практике:

  • ECH требует поддержки не только браузера, но и всей цепочки — DNS-резолвера с поддержкой нужных записей, а также сервера/CDN, который умеет расшифровать внешний ClientHello;
  • он эффективно работает в первую очередь там, где много разных доменов физически стоят за одной точкой терминации TLS (крупные CDN), потому что внешнее имя должно быть у многих сайтов общим, иначе само по себе становится идентификатором;
  • поддержка ECH в браузерах и на стороне серверов постепенно расширяется, но остаётся не универсальной и зависящей от конкретной конфигурации DNS и хостинга — рассчитывать, что она включена «по умолчанию везде», не стоит;
  • ECH не отменяет SNI как механизм — он не меняет то, что сервер должен выбрать сертификат на основе имени домена, а меняет только то, в каком виде это имя передаётся по сети.

Для большинства сайтов на обычном VPS-хостинге ECH пока не встаёт остро — куда практичнее задача — как раз без утечки лишнего трафика корректно обслужить несколько доменов по SNI. Но знать, что развитие идёт в сторону сокрытия и этого поля, полезно при выборе CDN или прокси-провайдера, если вопрос приватности домена принципиален.

Практические следствия для виртуального HTTPS-хостинга

Если вы администрируете сервер с несколькими HTTPS-сайтами, из механизма SNI следует несколько практических правил:

Один IP спокойно держит десятки и сотни сайтов. Никакого технического потолка «N доменов на IP» протокол не задаёт — предел определяется ресурсами сервера (память под TLS-сессии, число одновременных соединений, нагрузка терминации TLS) и удобством конфигурации, а не адресацией.

Старым клиентам без поддержки SNI тяжелее. Это редкость на современном вебе (актуальные браузеры, ОС и HTTP-клиенты SNI поддерживают), но если аудитория включает специфичные встраиваемые устройства или очень старое ПО — стоит явно продумать, что получит такой клиент на default_server.

Сертификаты нужно раздавать по каждому домену отдельно (или объединять через SAN/wildcard там, где это оправдано), а не рассчитывать на один сертификат «на всё». Let's Encrypt в связке с certbot или acme.sh прекрасно автоматизирует выпуск и продление для десятков доменов на одном сервере — но за актуальностью цепочки и сроками действия нужно следить: истечение сертификата на одном из доменов не затронет остальные, но сломает конкретно этот сайт. Если сертификат уже отозван, браузер и клиент узнают об этом не мгновенно — механизм проверки отзыва и его задержки разобраны в статье про OCSP-ответчик и девять секунд ожидания.

Первое HTTPS-соединение к новому домену на сервере чуть дороже по времени, чем последующие — не из-за SNI как такового, а из-за самого TLS-рукопожатия и, если используется резолвинг сертификата на лету, дополнительных операций сервера. Подробнее про то, почему именно первое обращение к HTTPS-сайту обходится дороже, — в статье про то, почему HTTPS дорогой только в первый раз.

Балансировщики и reverse-proxy тоже читают SNI. Если перед вашими бэкендами стоит HAProxy, Traefik или облачный балансировщик в режиме TCP-passthrough (без терминации TLS на самом балансировщике), маршрутизация по доменам на этом уровне обычно тоже строится на чтении SNI из ClientHello — это тот же механизм, просто на шаг раньше в цепочке.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

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

Нужен ли отдельный IP для каждого HTTPS-домена на VPS?

Нет, начиная с массовой поддержки SNI (она давно повсеместна у современных браузеров, ОС и HTTP-клиентов) один IP спокойно обслуживает множество HTTPS-доменов с разными сертификатами — сервер выбирает сертификат по имени домена из расширения SNI в ClientHello, а не по IP-адресу, на который пришло соединение.

Если провайдер видит SNI в открытом виде, значит ли это, что HTTPS бесполезен для приватности?

Нет. Содержимое запроса, тело страницы, cookies и заголовки остаются зашифрованы и недоступны наблюдателю — видно только сам факт обращения к конкретному домену. Encrypted Client Hello (ECH) — развивающийся механизм, который призван скрыть и это, но его поддержка пока не универсальна и зависит от конкретного DNS-резолвера и CDN.

Что получит клиент, который подключился без SNI (старое ПО)?

Сервер отдаст сертификат server{}-блока, помеченного как default_server (или первого подходящего по IP и порту), — если домен клиента не совпадает с этим сертификатом, браузер покажет предупреждение о несовпадении имени.

SNI и wildcard-сертификат — это одно и то же решение проблемы?

Нет. Wildcard-сертификат (*.example.com) закрывает все поддомены одного домена одним сертификатом, но не помогает разместить на одном IP разные домены — там всё равно нужны отдельные сертификаты, и именно SNI позволяет серверу выбрать нужный из них в момент рукопожатия.

Можно ли посмотреть, какой SNI отправляет мой клиент или что отдаёт сервер по конкретному имени?

Да, самый простой способ — openssl s_client -connect ip:443 -servername domain.com, который явно укажет запрошенное имя и покажет, какой сертификат сервер вернул именно для него.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →