MAATRIX / Блог / Сколько TLS-рукопожатий в секунду тянет ваш процессор: замер и цена шифрования

Сколько TLS-рукопожатий в секунду тянет ваш процессор: замер и цена шифрования

MAATRIX

Если сервис держит миллион открытых keep-alive соединений, TLS почти ничего не стоит — шифрование потока данных дешёвое, и один раз согласованный ключ работает часами. Другое дело — поток новых соединений: мобильное приложение без нормального connection pooling, публичный API, за которым клиенты заходят по одному запросу и отваливаются, или всплеск от ботов. Каждое новое соединение — это полноценное TLS-рукопожатие с асимметричной криптографией, а она на порядки дороже симметричного шифрования данных. Разберём, откуда берётся эта цена, чем resumption отличается от полного хендшейка по нагрузке, при чём тут AES-NI и как замерить реальный потолок именно на вашем сервере.

Почему TLS-хендшейк — дорогая операция для CPU

TLS решает две разные задачи, и стоят они CPU совершенно по-разному. Первая — зашифровать поток данных симметричным ключом (AES-GCM или ChaCha20-Poly1305): это дёшево, особенно с аппаратным ускорением, и стоимость растёт линейно с объёмом трафика. Вторая — согласовать этот самый симметричный ключ между сторонами, у которых изначально нет общего секрета. Вот здесь и живёт асимметричная криптография: обмен ключами через ECDHE (эллиптические кривые) и подпись сертификата сервера приватным ключом (RSA или ECDSA), которую клиент проверяет открытым ключом из сертификата.

Асимметричные операции — модульное возведение в степень для RSA или скалярное умножение точки для ECC — вычислительно тяжелее одного раунда AES на порядки. Подробный разбор самого протокола хендшейка, шаг за шагом от ClientHello до Finished, есть в статье про TLS-рукопожатие целиком — здесь важнее другое: эта дорогая операция выполняется один раз на новое соединение, а не один раз на байт трафика. Цена для CPU определяется не объёмом переданных данных и не числом одновременно открытых соединений, а именно частотой новых хендшейков в секунду — метрикой, которую редко смотрят в мониторинге, где обычно есть графики RPS и мегабит, но нет графика handshakes/sec.

Отсюда практическое следствие: сервис с тысячами держащихся keep-alive соединений и слабым притоком новых клиентов почти не тратит CPU на TLS. Тот же сервис с тем же числом новых соединений в секунду (характерно для коротких запросов без keep-alive, мобильных клиентов на нестабильной сети, где соединение регулярно рвётся и переустанавливается, или ботов, которые не умеют переиспользовать TCP) — это уже сопоставимое число асимметричных операций в секунду, и здесь CPU можно загрузить под завязку задолго до того, как упрётся сетевой канал.

Полный хендшейк против session resumption: в чём разница по нагрузке

TLS с самого начала предусматривает способ пропустить самую дорогую часть при повторном подключении того же клиента — session resumption в двух вариантах:

  • Session ID — сервер хранит состояние сессии в собственном кэше, привязанном к идентификатору, который отдаёт клиенту. При повторном подключении клиент присылает ID, сервер находит сессию в ssl_session_cache и восстанавливает ключи.
  • Session tickets (RFC 5077) — сервер шифрует состояние и отдаёт клиенту в виде билета, без хранения у себя. Удобнее для горизонтально масштабируемых бэкендов, но требует одинакового ticket-ключа на всех инстансах, иначе резюмирование не сработает на другом сервере.

В TLS 1.3 оба механизма унифицированы вокруг PSK-resumption, но суть та же: при resumed-соединении сервер не выполняет ни обмен ключом с нуля, ни подпись сертификата — только расшифровку билета или поиск в кэше (симметричные операции) и вывод новых ключей через HKDF из уже известного секрета. Асимметричная математика в этом пути не участвует вообще — именно поэтому resumed-хендшейк на порядки дешевле полного.

Практический нюанс для nginx — какой из двух механизмов реально работает:

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets on;

shared:SSL:10m даёт кэш сессий, общий для всех worker-процессов nginx на одном сервере (примерно 4000 сессий на 1 МБ), но не общий между разными серверами за балансировщиком: без sticky-сессий resumption-хит-рейт будет низким, потому что сессия с одного сервера не видна другому — вернёмся к этому в разделе про вынос TLS-терминации.

Проверить, сработало ли resumption, можно через openssl s_client:

openssl s_client -connect example.com:443 -reconnect -no_ign_eof </dev/null 2>&1 | grep -i "reused\|New,"

Флаг -reconnect устанавливает соединение несколько раз подряд; New в выводе — полный хендшейк, Reused — успешное resumption. Подробный разбор этого механизма с настройкой кэша — в статье почему HTTPS «дорогой» только в первый раз.

Отдельно стоит упомянуть 0-RTT в TLS 1.3: клиент с уже известным PSK отправляет данные приложения в первом же пакете, не дожидаясь ответа сервера. Это снижает задержку, но не саму цену CPU относительно обычного resumption, и несёт риск replay-атак — включать стоит только для идемпотентных запросов.

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

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

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

Роль аппаратного ускорения: что AES-NI ускоряет, а что нет

Здесь легко ошибиться: AES-NI — это набор инструкций CPU для аппаратного ускорения именно блочного шифра AES, то есть симметричной части TLS. Он даёт огромный выигрыш на шифровании потока данных после того, как ключ уже согласован, но никак не ускоряет саму асимметричную математику хендшейка — модульное возведение в степень для RSA или скалярное умножение точки для ECC выполняются совсем другими инструкциями. Подробный разбор того, что изменилось в CPU и протоколах, — в отдельной статье про AES-NI; здесь важнее прикладной вывод: если у вас упираются в CPU именно хендшейки, а не поток данных, включённый AES-NI сам по себе проблему не решит.

Что реально снижает цену асимметричной части:

  • Тип и размер ключа сертификата. Подпись ECDSA (P-256) заметно дешевле по CPU, чем RSA-2048, а RSA-4096 заметно дороже RSA-2048 — разница на порядок при переходе между вариантами. Первый практический шаг при упоре в CPU — проверить тип сертификата и рассмотреть переход на ECDSA, если клиенты это поддерживают.
  • Современные группы для обмена ключом. X25519 — быстрая программная кривая, спроектированная так, чтобы не требовать аппаратной поддержки; в актуальных версиях OpenSSL она обычно в приоритете у клиента.
  • Выделенные аппаратные ускорители инфраструктуры (например, Intel QuickAssist Technology) умеют разгружать и асимметричную криптографию, но это решение для по-настоящему большого масштаба, не типичный сценарий одного VPS.

Проверить аппаратную поддержку AES — секундное дело: lscpu | grep -i aes. Но если узкое место — именно частота новых соединений, следующий вопрос не «есть ли AES-NI», а «какой тип ключа на сертификате» и «включено ли resumption».

Как замерить нагрузку от TLS на своём сервере

Вместо того чтобы гадать, стоит измерить конкретно на своём железе.

1. Чистая скорость асимметричных операций на CPU, без сети и приложения — верхняя граница одного ядра:

openssl speed rsa2048
openssl speed ecdsap256

Команда прогоняет операции подписи/проверки заданное время и выводит число операций в секунду для конкретного алгоритма и ключа именно на вашем CPU — не абстрактный бенчмарк из чужой статьи, а число со своего железа. Умножив грубо на число ядер, отданных под TLS, получаете ориентировочный потолок по крипто-математике — верхнюю границу в идеальных условиях, не гарантированную пропускную способность: сеть и накладные расходы стека тоже съедают ресурс.

2. Реальные хендшейки против живого сервера — специально созданный для этого инструмент openssl s_time:

openssl s_time -connect example.com:443 -new -time 10
openssl s_time -connect example.com:443 -reuse -time 10

-new заставляет клиента устанавливать полностью новое соединение при каждом подключении и выводит число хендшейков в секунду; -reuse делает то же самое, переиспользуя TLS-сессию — прямое измерение выигрыша от resumption на вашем сервере, без домыслов.

3. Нагрузочный тест с контролем connection churn. h2load (из nghttp2-client) с флагом -m1 ограничивает число запросов на одно соединение единицей — почти каждый запрос становится новым хендшейком:

h2load -c200 -m1 -n20000 https://example.com/

Сравните CPU-профиль такого прогона с обычным wrk на keep-alive — разница и покажет реальную цену частых новых соединений на вашем стеке. Параллельно смотрите mpstat -P ALL 1: если %usr растёт равномерно на всех ядрах, CPU занят криптографией; если нагрузка концентрируется на одном-двух ядрах при простаивающих остальных — дело не в цене хендшейка, а в том, что воркеры не разложены по ядрам.

4. Доля resumption в реальном трафике, не в синтетике. В nginx это переменная $ssl_session_reused в логах:

log_format tls_stats '$remote_addr $ssl_session_reused $ssl_protocol';
access_log /var/log/nginx/tls_stats.log tls_stats;

Низкий процент resumed-соединений при повторных заходах клиентов — повод проверить ssl_session_timeout, размер кэша и, при нескольких бэкендах, синхронизацию ticket-ключа между ними.

Сколько рукопожатий в секунду тянет сервер: от чего это зависит

Конкретное число здесь намеренно не приводится — любая цифра из чужого бенчмарка будет вводить в заблуждение сильнее, чем помогать, потому что потолок зависит от комбинации факторов, разных на каждом сервере:

  • Число ядер CPU. Асимметричная операция хорошо параллелится между worker-процессами — условно, вдвое больше ядер под TLS даёт примерно вдвое больше хендшейков в секунду, если ничего другого не упирается раньше.
  • Тип и размер ключа сертификата — ECDSA против RSA-2048 против RSA-4096 дают разницу на порядок, как отмечено выше.
  • Доля resumed-соединений в трафике. Смешанный трафик с высоким процентом resumption на порядок дешевле по CPU, чем трафик из одних новых соединений — поэтому вопрос «сколько хендшейков в секунду» без уточнения «полных или с resumption» не вполне корректен.
  • Версия TLS. TLS 1.3 сокращает число round-trip'ов относительно TLS 1.2, снижая задержку, но не меняет фундаментально саму цену асимметричной операции в тактах CPU.
  • Что упирается раньше крипто-математики. На части конфигураций сервер упрётся не в асимметричные операции, а в лимиты файловых дескрипторов, размер conntrack-таблицы или накладные расходы TCP/IP-стека — раньше, чем проявится цена RSA или ECDHE. Различить это можно только измерением, а не прикидкой на бумаге.

Практичный способ получить свою цифру — прогнать openssl speed для своего типа ключа (теоретический потолок одного ядра), затем openssl s_time -new против реального сервера (насколько теория совпадает с практикой — обычно ниже теоретического потолка), и параллельно смотреть mpstat, чтобы понять, упор действительно в CPU, а не в файловые дескрипторы или сеть.

Когда стоит выносить TLS-терминацию на отдельный балансировщик или прокси

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

  • Замеры показывают, что CPU упирается именно во время всплесков новых соединений, а не равномерно вместе с ростом трафика приложения — это отличает проблему хендшейков от обычного роста нагрузки на бизнес-логику.
  • Горизонтальное масштабирование бэкендов не убирает симптом. Если у каждого инстанса свой маленький ssl_session_cache, добавление ещё одного инстанса размножает проблему низкого resumption hit rate вместо решения — сессия с одного инстанса не видна другим при round-robin без sticky-сессий.
  • Профиль трафика — много коротких соединений от разных клиентов. Публичные API с мобильными клиентами на нестабильной сети, IoT-флот, который подключается редко и ненадолго, сервис без нормального connection pooling у клиентов — всё это создаёт постоянный поток новых хендшейков вместо небольшого числа держащихся keep-alive соединений.

Что даёт вынос TLS-терминации на отдельный слой (например, HAProxy или nginx перед пулом бэкендов — сравнение вариантов есть в статье HAProxy или nginx для балансировки):

  • Изоляция CPU-нагрузки — всплеск новых соединений грузит только терминирующий слой и не отбирает такты у процессов, которые считают бизнес-логику или ходят в базу.
  • Централизованный кэш сессий — один ssl_session_cache на весь слой вместо крошечного кэша на каждый бэкенд резко поднимает hit rate resumption независимо от того, куда балансировщик отправит следующий HTTP-запрос.
  • Независимое масштабирование узкого места — мощности добавляются именно в терминирующий слой, без изменения размера пула серверов приложения.
  • Единое место управления сертификатами вместо синхронизации по всем бэкендам.

Оборотная сторона: это перенос проблемы на специально выделенный и правильно посчитанный ресурс, а не бесплатное решение. Появляется дополнительный сетевой прыжок (обычно на локальной сети, задержка минимальна, но не нулевая), и сам терминирующий слой становится узлом, который тоже нужно мониторить на тот же потолок хендшейков в секунду — просто теперь его можно масштабировать отдельно и осознанно. Если сегменты между слоями разнесены географически или сеть не доверенная — стоит отдельно продумать mTLS или VPN между слоями, это уже отдельный вопрос архитектуры.

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

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

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

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

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

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

Если увеличить RSA-ключ до 4096 бит вместо 2048, сильно ли вырастет нагрузка?

Да, заметно — подпись с ключом 4096 бит ощутимо тяжелее для CPU, чем с 2048 бит, на порядок больше вычислений. Если частота новых соединений высокая, а требований к такому размеру ключа нет, разумно остаться на 2048 бит или перейти на ECDSA — она дешевле по CPU при сопоставимой стойкости.

AES-NI поможет, если упор именно в частоту хендшейков?

Не напрямую. AES-NI ускоряет симметричное шифрование данных после установления соединения, а не асимметричную математику самого хендшейка. Если узкое место — новые соединения, смотрите на тип ключа сертификата и долю resumption, а не на AES-NI.

Ticket-based resumption безопаснее session ID?

Уровень защищённости схожий при правильной настройке; у ticket-based есть нюанс — ticket-ключ нужно периодически ротировать и, при нескольких бэкендах, синхронизировать между ними явно (ssl_session_ticket_key с общим файлом), иначе клиент на другом инстансе получит полный хендшейк вместо resumed.

Стоит ли включать TLS 1.3 0-RTT ради снижения нагрузки?

0-RTT снижает задержку для повторных соединений, но не цену CPU относительно обычного resumption, и добавляет риск replay-атак для неидемпотентных запросов — включать стоит осознанно, не как способ «снять нагрузку».

Нужен ли отдельный балансировщик для TLS, если у меня один VPS и небольшой проект?

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

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

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

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