MAATRIX / Блог / Сколько виртуальных хостов на одном IP: SNI, сертификаты и настоящая граница

Сколько виртуальных хостов на одном IP: SNI, сертификаты и настоящая граница

MAATRIX

Вопрос «сколько HTTPS-сайтов можно повесить на один IP» звучит так, будто где-то в спецификации TLS зашито конкретное число. Его там нет — SNI снял привязку сертификата к адресу, и формального потолка на количество виртуальных хостов не существует. Но это не значит, что предела нет вообще: он просто переехал из протокола в память процесса, в скорость поиска нужного конфига и в состав клиентов, которые к вам приходят. Разберём, где именно проходит настоящая граница и что реально её сдвигает.

Откуда взялась иллюзия «один IP — один HTTPS-сайт»

До массового распространения SNI (Server Name Indication) у сервера действительно не было способа узнать, какой сертификат отдавать, до того как клиент назовёт домен — а называет он его уже внутри TLS-сессии, когда сертификат отправлять уже поздно. Единственным решением было развести сайты по разным IP-адресам и определять сертификат по тому, на какой адрес пришло соединение. Подробно эта механика и то, как SNI решает противоречие «сертификат нужен раньше домена», разобраны в статье про SNI и то, как один IP держит сотню HTTPS-сайтов — здесь не будем повторять эту часть, а сразу перейдём к вопросу масштаба.

Итог для сегодняшнего дня: SNI поддерживают практически все действующие браузеры, мобильные ОС и HTTP-клиенты уже больше десяти лет, и один IP-адрес технически может обслуживать любое число HTTPS-доменов с разными сертификатами. Вопрос «сколько влезет» — это не вопрос протокола, а вопрос конкретного сервера под конкретной нагрузкой.

Что на самом деле лежит в памяти на каждый виртуальный хост

Когда nginx стартует, он читает все server{}-блоки и строит внутреннюю структуру: для каждого TLS-хоста нужен SSL-контекст (SSL_CTX) — объект, в котором библиотека (обычно OpenSSL) держит загруженный сертификат, приватный ключ, параметры протокола и шифров для этого конкретного блока.

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

Каждый такой блок — это отдельный SSL_CTX в памяти master-процесса, который потом наследуют воркеры. Сам по себе объект некрупный: сертификат и ключ — это единицы-десятки килобайт на файл, а не мегабайты. Даже несколько тысяч таких контекстов на сервере с достаточным запасом RAM не станут узким местом сами по себе — конкретную цифру для своей конфигурации и своей версии OpenSSL стоит измерить самостоятельно, потому что она зависит от алгоритма ключа (RSA заметно тяжелее ECDSA), включённых шифров и версии библиотеки.

Настоящий расход памяти растёт не от числа доменов, а от числа активных TLS-сессий:

ssl_session_cache shared:SSL:20m;
ssl_session_timeout 1d;

ssl_session_cache — это общий пул памяти на все домены сразу, где хранятся параметры сессий для их последующего быстрого возобновления (session resumption) без полного handshake. Его размер задаётся в мегабайтах и напрямую определяет, сколько сессий поместится, независимо от того, сколько у вас server{}-блоков. При сотнях низкотрафиковых доменов кеш не успевает заполняться под завязку; при высоком суммарном трафике по всем доменам вместе — именно этот кеш, а не количество сайтов, стоит увеличивать первым. О том, как вообще устроено TLS-рукопожатие и что в нём стоит дорого по CPU, — в статье про TLS-рукопожатие и что происходит до первого байта.

Практический вывод: число виртуальных хостов почти не влияет на базовую память сервера. Влияет суммарный трафик, доля новых (не resumed) хендшейков и размер ssl_session_cache — считать надо по ним, а не по счётчику доменов в sites-enabled/.

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

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

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

Поиск нужного конфига: hash-таблицы vs линейный перебор

Второе место, где количество доменов действительно имеет значение — не память, а скорость, с которой nginx находит нужный server{}-блок по имени из SNI. По умолчанию nginx строит для точных имён доменов хеш-таблицы: server_names_hash, map_hash, — и подбирает их размер под общий объём загруженных имён при старте. Это быстро и почти не зависит от числа хостов на разумных масштабах, но упирается в фиксированный размер таблицы, если доменов резко становится много:

nginx: [emerg] could not build the server_names_hash, you should increase server_names_hash_max_size: 512

Эта ошибка — не про нехватку памяти сервера, а про то, что таблица имён домена не влезла в изначально выделенный размер. Лечится явным увеличением директив в http{}:

http {
    server_names_hash_max_size 4096;
    server_names_hash_bucket_size 128;
}

Отдельно стоит регулярка в server_name: если вместо точного имени или простого wildcard-шаблона (*.example.ru) используется регулярное выражение, nginx не может положить такое имя в хеш-таблицу и вынужден проверять регулярки последовательно, одну за другой, пока не найдёт совпадение. При десятке доменов разница не заметна. При тысячах виртуальных хостов, где часть настроена регулярками, а часть — точными именами, порядок объявления server{}-блоков и доля регулярок в конфиге начинают реально сказываться на времени выбора хоста на каждое новое TLS-соединение — не критично, но измеримо, если у вас достаточно высокий поток новых подключений. Практический совет: держите регулярки в server_name только там, где без них не обойтись, а основную массу доменов описывайте точными именами или простым wildcard — так nginx использует быстрый хеш-поиск, а не перебор.

Клиенты без поддержки SNI: кто ещё встречается и что с ними делать

SNI существует с середины 2000-х и поддерживается всеми современными браузерами, мобильными платформами и подавляющим большинством HTTP-библиотек. Но полностью списывать клиентов без SNI со счетов рано — на практике встречаются:

  • очень старые версии Android (ниже 3.0) и старые версии Java (до обновлений Java 7), которые до сих пор изредка попадаются в корпоративных встроенных системах, платёжных терминалах и legacy-интеграциях;
  • некоторые примитивные HTTP-клиенты, боты и сканеры, написанные без поддержки TLS-расширений;
  • часть корпоративных прокси и антивирусных шлюзов старых версий, перехватывающих TLS-трафик собственной реализацией.

Когда такой клиент подключается без SNI, nginx не может выбрать сертификат по имени домена — и отдаёт сертификат из блока, помеченного default_server, либо из первого подходящего по адресу и порту server{}-блока, если явной пометки нет:

server {
    listen 443 ssl default_server;
    server_name _;
    ssl_certificate     /etc/letsencrypt/live/main-domain.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/main-domain.ru/privkey.pem;
    return 444;
}

Если домен клиента не совпадает с именем в отданном сертификате, браузер (у современных клиентов) покажет предупреждение о несовпадении имени. Для реального продакшена стоит осознанно решить, что должно происходить с такими подключениями: часть проектов держит на default_server самый приоритетный домен, часть — явную заглушку с return 444, которая просто обрывает соединение без ответа. Для подавляющего большинства сегодняшних сайтов доля трафика без SNI пренебрежимо мала, но если среди ваших пользователей есть встроенные или промышленные системы со старыми TLS-стеками — это стоит проверить заранее, а не после жалобы клиента.

Wildcard и SAN-сертификаты: как сократить число объектов, а не число доменов

SNI решает вопрос «какой сертификат показать», но не отменяет вопрос «сколько отдельных сертификатов вообще нужно выпускать и продлевать». Здесь в игру вступают два инструмента, которые снижают административную нагрузку при росте числа виртуальных хостов, но по-разному.

Wildcard-сертификат (*.example.ru) закрывает одним сертификатом все поддомены одного домена первого уровня. Он не помогает, если на сервере живут разные домены (shop.ru и example.net) — для них всё равно нужны отдельные сертификаты, и именно SNI позволяет серверу выбрать нужный из нескольких. Зато если у вас сотня поддоменов одного проекта (client1.example.ru, client2.example.ru...), wildcard сводит их обслуживание к одному объекту продления вместо сотни:

server {
    listen 443 ssl;
    server_name *.example.ru;
    ssl_certificate     /etc/letsencrypt/live/example.ru-wildcard/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.ru-wildcard/privkey.pem;
}

Важный нюанс: получить wildcard-сертификат у Let's Encrypt можно только через DNS-01 подтверждение (запись TXT в зоне) — HTTP-01, который используется для обычных сертификатов, для wildcard не работает.

SAN-сертификат (Subject Alternative Name, он же multi-domain) — один сертификат, в котором перечислен список конкретных доменов, необязательно связанных общим родителем: shop.ru, example.net, partner-site.com в одном файле. Это сокращает число объектов продления, но увеличивает радиус поражения: если такой сертификат не продлился вовремя или скомпрометирован, падают все домены в нём разом, а не один. У SAN-сертификатов есть и практический потолок числа доменов внутри одного сертификата у большинства CA — конкретное число стоит уточнять у своего удостоверяющего центра на момент выпуска, оно у разных CA и разных типов сертификатов не одинаковое.

Выбор между «отдельный сертификат на домен», wildcard и SAN — это не вопрос производительности сервера (SNI одинаково быстро отдаёт что маленький, что объединённый сертификат), а вопрос эксплуатации: сколько объектов вы готовы продлевать по отдельности и какой ущерб приемлем при сбое продления одного из них.

Как измерить реальный потолок на своём сервере, а не гадать

Вместо абстрактного «сколько хостов нормально» разумнее пройти по конкретным точкам на своей машине:

  1. Память под воркеры под нагрузкой — базовая проверка, не специфична для TLS, но нужна как отправная точка:
ps aux | grep 'nginx: worker' | awk '{sum+=$6} END {print sum/1024, "MB"}'
  1. Время проверки и перечитывания конфигурации — растёт вместе с числом блоков и регулярок в server_name:
time nginx -t
  1. Размер и заполненность SSL-кеша сессий — через переменную $ssl_session_reused в логе, если её добавить в log_format. Высокая доля новых (не resumed) хендшейков при низком трафике на домен — сигнал, что кеш сессий слишком мал или таймаут слишком короткий для вашего профиля посещаемости.
  1. Проверка выбора сертификата по SNI для конкретного домена — вручную, без браузера:
openssl s_client -connect 203.0.113.10:443 -servername shop.example.ru </dev/null 2>/dev/null | openssl x509 -noout -subject -dates

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

  1. Явная проверка, что каждый сертификат отслеживается на срок действия — при десятках доменов на одном IP ручной контроль перестаёт работать, и нужен отдельный мониторинг, а не память администратора; подход разобран в статье мониторинг сертификатов и доменов.

Если по итогам память в порядке, nginx -t отрабатывает быстро, доля resumed-сессий высокая, а SNI на выборочных доменах отдаёт правильные сертификаты — технических причин ограничивать себя числом виртуальных хостов на IP нет. Упрётесь вы, скорее всего, не в протокол и не в SNI, а в суммарный трафик всех доменов сразу — вопрос, который к количеству хостов имеет лишь косвенное отношение и разобран отдельно в материале сколько доменов на одном сервере — где кончается nginx.

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

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

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

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

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

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

Есть ли у SNI или у TLS вообще жёсткий лимит числа доменов на IP?

Нет, ни протокол, ни расширение SNI такого лимита не задают. Ограничения приходят от конкретной реализации сервера (память, размер хеш-таблиц конфигурации) и от суммарной нагрузки, а не от числа записей самого по себе.

Wildcard-сертификат снижает нагрузку на сервер по сравнению с отдельными сертификатами на каждый домен?

Практически нет — SNI одинаково быстро выбирает что один сертификат на несколько поддоменов, что отдельный сертификат на каждый. Wildcard экономит не ресурсы сервера, а количество объектов, которые нужно продлевать и администрировать.

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

Сервер отдаст сертификат из блока default_server (или первого подходящего по адресу и порту), и если имя в этом сертификате не совпадёт с ожидаемым доменом клиента, современный браузер покажет предупреждение о несовпадении имени.

Нужно ли увеличивать server_names_hash_max_size заранее, если планирую много доменов?

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

SAN-сертификат на много доменов — это удобнее, чем отдельные сертификаты?

Удобнее с точки зрения числа объектов продления, но опаснее с точки зрения отказоустойчивости: один просроченный или скомпрометированный SAN-сертификат кладёт сразу все домены в нём, а не один.

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

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

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