Выберите продукт

SNI в TLS: несколько сертификатов на одном IP в Nginx, HAProxy и Apache

SNI позволяет обслуживать несколько HTTPS‑доменов на одном IP: имя хоста передаётся в TLS ClientHello ещё до HTTP. Разберём ограничения, типовые ошибки и рабочие конфиги для Nginx, HAProxy (crt-list) и Apache, плюс тесты через openssl.
SNI в TLS: несколько сертификатов на одном IP в Nginx, HAProxy и Apache

Когда на одном сервере живут несколько сайтов, удобно обойтись одним публичным IP-адресом и при этом раздавать «правильный» TLS-сертификат для каждого домена. Именно это и делает SNI (Server Name Indication): клиент сообщает имя хоста во время TLS-рукопожатия, и сервер выбирает подходящий сертификат ещё до того, как начнётся HTTP.

Ниже — как устроен SNI на практике, какие есть ограничения, и рабочие примеры конфигураций для Nginx, HAProxy (через crt-list) и Apache. В конце — диагностика: что проверять, если «настроил, а сертификат не тот».

Что такое SNI и почему без него было сложно

Исторически TLS-сервер должен был выбрать сертификат до того, как увидит HTTP-заголовок Host. Из-за этого классический вариант «несколько HTTPS-сайтов на одном порту» часто требовал несколько IP: сервер просто не знал, под какой домен пришло соединение.

SNI добавляет в ClientHello поле с именем (hostname). Сервер (или TLS-терминатор/прокси) сопоставляет это имя с виртуальным хостом и выбирает нужный сертификат. В результате схема становится простой: много доменов → один IP → один 443 → разные сертификаты.

Как это выглядит по шагам

  1. Клиент подключается к вашему IP:443.
  2. Отправляет ClientHello с SNI-именем (например, example.com).
  3. Сервер/прокси выбирает сертификат для example.com и продолжает TLS.
  4. Только потом начинается HTTP и приходит Host: example.com.

Ключевой момент: выбор сертификата происходит на уровне TLS, до HTTP. Поэтому «выбрать сертификат по URL» невозможно — только по имени хоста (SNI).

Ограничения и подводные камни SNI

SNI поддерживается практически всеми современными браузерами и библиотеками, но «краевые» проблемы всё ещё встречаются. Ниже — то, что чаще всего ломает ожидания.

1) Старые клиенты без SNI

Если клиент не отправляет SNI, сервер не понимает, какой домен нужен, и отдаёт «дефолтный» сертификат. Сегодня это редко для браузеров, но встречается в:

  • старых встроенных устройствах;
  • части кастомных HTTP-клиентов;
  • устаревших Java/openssl-стэках (зависит от версий и сборок).

Практический вывод: держите дефолтный сертификат корректным и предсказуемым, а для критичных «несовместимых» клиентов иногда проще выделить отдельный IP.

2) Несовпадение SNI и HTTP Host

Иногда приходит одно имя в SNI, а в HTTP заголовке Host — другое (ошибка клиента, промежуточный прокси, криво настроенная миграция). На стороне сервера TLS-контекст уже выбран, и вы получаете:

  • ошибки 400/421 (в зависимости от того, как настроена проверка и дефолтный обработчик);
  • или попадание на «не тот» сайт, если дефолтный vhost делает редиректы или отдаёт контент.

Если у вас несколько уровней проксирования, лучше явно определять default-поведение на 443 и не пытаться «лечить» mismatch редиректами.

3) Цепочка сертификатов и порядок в файле

Одна из самых частых причин сообщений вида «incomplete chain»: сервер отдаёт leaf-сертификат, но не отдаёт промежуточные (intermediate) или они склеены в неправильном порядке. В типовом случае корректно так:

  • сначала сертификат домена (leaf);
  • затем промежуточные сертификаты (один или несколько);
  • корневой сертификат обычно не добавляют (он есть в хранилище доверия у клиента).

Если вы используете коммерческие сертификаты, заранее проверьте, что отдаёте полный fullchain (для Nginx) или корректный единый PEM (для HAProxy), либо правильно подключаете chain в Apache. Если сертификата ещё нет, удобнее заранее подобрать подходящие SSL-сертификаты под все нужные имена (SAN/wildcard), чтобы не городить лишние vhost’ы и исключения.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Схема TLS-рукопожатия: SNI передаётся в ClientHello до начала HTTP

Nginx: несколько сертификатов через server blocks

В Nginx SNI «встроен» в модель server-блоков. Достаточно описать разные server_name и указать соответствующие ssl_certificate и ssl_certificate_key.

Базовый пример на два домена

server {
  listen 443 ssl;
  server_name site-a.example;

  ssl_certificate /etc/nginx/ssl/site-a/fullchain.pem;
  ssl_certificate_key /etc/nginx/ssl/site-a/privkey.pem;

  location / {
    proxy_pass http://127.0.0.1:8081;
  }
}

server {
  listen 443 ssl;
  server_name site-b.example;

  ssl_certificate /etc/nginx/ssl/site-b/fullchain.pem;
  ssl_certificate_key /etc/nginx/ssl/site-b/privkey.pem;

  location / {
    proxy_pass http://127.0.0.1:8082;
  }
}

Дефолтный сертификат на случай клиентов без SNI

Чтобы контролировать поведение без SNI (или при неизвестном имени), заведите отдельный default-сервер. Именно он станет кандидатом на выдачу сертификата, если SNI отсутствует или не совпало ни с одним server_name.

server {
  listen 443 ssl default_server;
  server_name _;

  ssl_certificate /etc/nginx/ssl/default/fullchain.pem;
  ssl_certificate_key /etc/nginx/ssl/default/privkey.pem;

  return 444;
}

Код 444 специфичен для Nginx: соединение закрывается без ответа. Это удобно против мусорных запросов, но для некоторых бизнес-сценариев лучше вернуть 400/421 или отдать нейтральную страницу.

Перечитать сертификаты без остановки сервиса

nginx -t
nginx -s reload

HAProxy: SNI-терминация и crt-list

HAProxy часто ставят на «первый рубеж»: он принимает TLS, выбирает сертификат по SNI и проксирует запрос дальше. Для управления множеством сертификатов удобно использовать список — crt-list.

Как должен выглядеть PEM для HAProxy

Классический вариант для HAProxy — один PEM-файл, в котором подряд лежат приватный ключ, сертификат домена и промежуточные сертификаты. Если у вас ключ и сертификаты раздельно, их обычно склеивают в один файл в правильном порядке (leaf → intermediate).

Пример crt-list

Создадим файл /etc/haproxy/crt-list.txt:

/etc/haproxy/certs/site-a.pem site-a.example www.site-a.example
/etc/haproxy/certs/site-b.pem site-b.example

Первое поле — путь к PEM. Далее (через пробел) можно перечислять имена, для которых сертификат допустим. Это удобно для SAN и wildcard.

Фронтенд с выбором бэкенда по SNI

frontend fe_https
  bind :443 ssl crt-list /etc/haproxy/crt-list.txt alpn h2,http/1.1
  mode http

  http-request set-header X-Forwarded-Proto https

  use_backend be_site_a if { ssl_fc_sni -i site-a.example www.site-a.example }
  use_backend be_site_b if { ssl_fc_sni -i site-b.example }

  default_backend be_default

backend be_site_a
  mode http
  server s1 127.0.0.1:8081

backend be_site_b
  mode http
  server s1 127.0.0.1:8082

backend be_default
  mode http
  http-request deny deny_status 421

Маршрутизация по ssl_fc_sni часто надёжнее, чем по HTTP Host: к моменту HTTP TLS уже завершён, и сертификат выбран. Если у вас дальше цепочка прокси, следите, чтобы они корректно передавали Host и/или X-Forwarded-Host туда, где это важно для приложения.

Проверка и «мягкая» перезагрузка

haproxy -c -f /etc/haproxy/haproxy.cfg

Перезагрузку в продакшене обычно делают через systemd reload (если юнит настроен на seamless reload), чтобы не рвать активные соединения.

Apache: SNI через VirtualHost на 443

В Apache SNI реализован через name-based virtual hosts на *:443. Важно, чтобы был включён SSL-модуль и виртуальные хосты на TLS-порту описаны корректно.

Минимальный пример двух vhost’ов

<VirtualHost *:443>
  ServerName site-a.example

  SSLEngine on
  SSLCertificateFile /etc/apache2/ssl/site-a/cert.pem
  SSLCertificateKeyFile /etc/apache2/ssl/site-a/privkey.pem
  SSLCertificateChainFile /etc/apache2/ssl/site-a/chain.pem

  ProxyPreserveHost On
  ProxyPass / http://127.0.0.1:8081/
  ProxyPassReverse / http://127.0.0.1:8081/
</VirtualHost>

<VirtualHost *:443>
  ServerName site-b.example

  SSLEngine on
  SSLCertificateFile /etc/apache2/ssl/site-b/cert.pem
  SSLCertificateKeyFile /etc/apache2/ssl/site-b/privkey.pem
  SSLCertificateChainFile /etc/apache2/ssl/site-b/chain.pem

  ProxyPreserveHost On
  ProxyPass / http://127.0.0.1:8082/
  ProxyPassReverse / http://127.0.0.1:8082/
</VirtualHost>

Директивы могут отличаться в зависимости от версии Apache и вашей практики хранения цепочки. В некоторых сборках цепочку добавляют прямо в файл сертификата, и отдельный SSLCertificateChainFile не требуется.

Дефолтный VirtualHost

Apache выбирает «первый» vhost на *:443 как дефолтный. Поэтому для предсказуемости держите первым нейтральный vhost-заглушку или отдельный «default» сайт, чтобы без SNI не было сюрпризов.

Проверка: какой сертификат реально отдаётся по SNI

Проверять удобнее не браузером, а инструментами, где можно явно задать SNI. Самый универсальный вариант — openssl s_client.

OpenSSL s_client с явным SNI

openssl s_client -connect 203.0.113.10:443 -servername site-a.example -showcerts

Смотрите на:

  • Subject/SAN у leaf-сертификата;
  • цепочку (присутствуют ли intermediate);
  • ALPN (предлагается ли h2).

Проверка «что будет без SNI»

openssl s_client -connect 203.0.113.10:443 -showcerts

Так вы увидите, какой сертификат выдаётся по умолчанию. Это важный тест и для старых клиентов, и для отлова «случайного default» в конфиге.

Если сертификат «не тот»: быстрый чеклист

  • Кто у вас default на 443: в Nginx это default_server, в Apache — первый vhost, в HAProxy — первый подходящий сертификат из bind/crt-list.
  • Совпадает ли имя: точное ли server_name/ServerName, учтён ли www, нет ли опечаток.
  • Один ли это адрес: нет ли разных listen/bind/Listen на разных IP или в разных конфиг-файлах.
  • Цепочка: отдаётся ли intermediate и в правильном порядке.
  • Применился ли конфиг: nginx -t, проверка конфигурации HAProxy, reload/restart.

Где лучше завершать TLS: практический выбор

Выбор между Nginx/HAProxy/Apache как TLS-терминатором — это про архитектуру, а не про «как правильнее». Ориентируйтесь на место в цепочке и требования к маршрутизации.

TLS в Nginx

Хороший вариант для небольшого и среднего числа доменов: простая модель server-блоков, удобно рулить редиректами, заголовками и проксированием.

TLS в HAProxy

Подходит, когда нужен L7-балансировщик, много бэкендов и хочется маршрутизировать по SNI ещё до HTTP. Плюс удобно централизованно вести сертификаты через crt-list.

TLS в Apache

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

Если вы планируете выносить TLS на отдельный слой (балансировщик/edge) или просто нужен изолированный сервер под проксирование и сертификаты, удобнее делать это на VDS: меньше ограничений по конфигам, портам и пакетам, чем на shared-окружении.

Проверка SNI и цепочки сертификатов через openssl s_client в терминале

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Типовые ошибки и как их избежать

Ошибка 1: «На одном домене всё ок, на другом отдаётся чужой сертификат»

Почти всегда причина одна из трёх:

  • клиент без SNI (сверяйте тестом без -servername);
  • дефолтный vhost/server перехватывает неизвестное имя;
  • не учли фактическое имя (например, пользователи идут на www, а вы настроили только корень).

Ошибка 2: «Сертификат правильный, но браузер пишет incomplete chain»

Проверьте цепочку. В Nginx обычно используют fullchain в ssl_certificate, в HAProxy — единый PEM с ключом и intermediate, в Apache — корректное подключение chain (или склейка, если так принято в вашей сборке).

Ошибка 3: «После обновления сертификата показывается старый»

  • Убедитесь, что обновили именно тот файл, который указан в конфиге (не соседний, не symlink на старую цель).
  • Сделайте reload, чтобы процесс перечитал сертификаты.
  • Проверьте, нет ли перед вами второго TLS-терминатора (ещё один балансировщик/прокси), который продолжает отдавать старый сертификат.

Итоги

SNI — стандартный и надёжный способ обслуживать несколько HTTPS-доменов на одном IP. В Nginx это решается разными server-блоками с ssl_certificate, в HAProxy удобно вести сертификаты через crt-list и маршрутизировать по ssl_fc_sni, а в Apache SNI работает через VirtualHost на *:443.

Если выстроить понятный default-обработчик, следить за цепочками и проверять поведение через openssl s_client -servername, большинство «мистических» проблем исчезают ещё до продакшена.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: nginx bind() to 0.0.0.0:80 failed (98: Address already in use) — как найти и устранить конфликт порта OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: nginx bind() to 0.0.0.0:80 failed (98: Address already in use) — как найти и устранить конфликт порта

Ошибка nginx bind() to 0.0.0.0:80 failed (98: Address already in use) в Debian/Ubuntu почти всегда означает конфликт за 80 или 443 ...
Debian/Ubuntu: как исправить Nginx no live upstreams while connecting to upstream OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Nginx no live upstreams while connecting to upstream

Ошибка Nginx no live upstreams while connecting to upstream означает, что веб-сервер не видит доступных backend-процессов. Ниже — ...
ERR_TOO_MANY_REDIRECTS в Nginx и Apache за reverse proxy на Debian/Ubuntu: где искать цикл редиректов OpenAI Статья написана AI (GPT 5)

ERR_TOO_MANY_REDIRECTS в Nginx и Apache за reverse proxy на Debian/Ubuntu: где искать цикл редиректов

Если сайт уходит в ERR_TOO_MANY_REDIRECTS, причина обычно в конфликте редиректов между Nginx, Apache, приложением, CDN или reverse ...