Ошибка TLS-рукопожатия: причины и способы исправить
Ошибка TLS-рукопожатия появляется, когда клиент и сервер не смогли договориться о безопасном HTTPS-соединении. Для пользователя это выглядит как «сайт не открывается», ERR_SSL_PROTOCOL_ERROR, SSL_ERROR_HANDSHAKE_FAILURE_ALERT, TLS handshake failed или похожее сообщение. Для администратора — как сбой на уровне, который происходит ещё до HTTP-запроса: до GET /, до ответа 200, 301 или 500.
Главная сложность в том, что ошибка TLS-рукопожатия не указывает на одну конкретную причину. Это может быть просроченный сертификат, неполная цепочка, неправильный server_name в Nginx, отсутствие SNI, несовместимые версии TLS, проблемы на CDN, балансировщике, фаерволе или даже расхождение системного времени на клиенте.
Разберём, как работает рукопожатие TLS, где оно чаще всего ломается и как быстро сузить круг причин. Материал рассчитан на владельцев сайтов, разработчиков и DevOps-инженеров: с примерами команд, логов и практическим чеклистом исправления.
Что означает ошибка TLS-рукопожатия
TLS-рукопожатие — это начальная фаза HTTPS-соединения. Клиент и сервер ещё не передают обычные HTTP-данные, а договариваются о параметрах защищённого канала.
Упрощённо процесс выглядит так:
1) Клиент отправляет ClientHello — сообщает поддерживаемые версии TLS, наборы шифров, расширения, имя домена через SNI, поддерживаемые протоколы через ALPN, например h2 или http/1.1.
2) Сервер отвечает ServerHello — выбирает версию TLS, шифр, отдаёт сертификат и дополнительные данные для обмена ключами.
3) Клиент проверяет сертификат — смотрит срок действия, доменное имя, цепочку доверия, корневой центр сертификации, иногда статус отзыва.
4) Стороны согласуют ключи — после этого появляется защищённый канал, и только затем начинается обычный HTTP-обмен.
Если сбой происходит на любом из этих этапов, клиент сообщает об ошибке TLS handshake. Подробный технический разбор пакетов есть в статье TLS handshake под микроскопом: разбор по шагам с tcpdump, а базовую картину HTTPS, SSL, TLS и сертификатов можно освежить в материале Как работает HTTPS. Разбираемся в SSL, TLS и сертификатах.
Отдельный момент: TLS-ошибка — это не HTTP-ошибка. Сервер может быть жив, порт 443 открыт, Nginx запущен, но браузер всё равно не покажет страницу, потому что защищённое соединение не собрано.
Как выглядит ошибка в браузере, curl и логах
Формулировки зависят от клиента, библиотеки и ОС. Один и тот же сбой может называться по-разному.
1) В браузерах — Chrome часто показывает ERR_SSL_PROTOCOL_ERROR, ERR_CERT_COMMON_NAME_INVALID, ERR_CERT_DATE_INVALID, ERR_SSL_VERSION_OR_CIPHER_MISMATCH. Firefox — SSL_ERROR_HANDSHAKE_FAILURE_ALERT, SEC_ERROR_EXPIRED_CERTIFICATE, SSL_ERROR_UNSUPPORTED_VERSION. Safari может ограничиться сообщением о невозможности установить безопасное соединение.
2) В curl — ошибки обычно полезнее, потому что содержат код библиотеки TLS:
curl -Iv https://example.comТипичные варианты:
SSL certificate problem: certificate has expired;SSL: no alternative certificate subject name matches target host name;OpenSSL SSL_connect: SSL_ERROR_SYSCALL;error:0A00010B:SSL routines::wrong version number;gnutls_handshake() failed;tlsv1 alert protocol version.
Если вы редко используете этот инструмент, пригодится отдельный разбор: Что такое curl и как им пользоваться.
3) В логах Nginx или балансировщика — можно увидеть SSL_do_handshake() failed, no shared cipher, unsupported protocol, client sent plain HTTP request to HTTPS port, peer closed connection in SSL handshake.
4) В приложениях — Node.js может писать EPROTO, Java — javax.net.ssl.SSLHandshakeException, Go — remote error: tls: handshake failure, Python — ssl.SSLError.
Формулировка важна, но не стоит диагностировать только по ней. Например, handshake failure может быть вызван и шифрами, и сертификатом, и тем, что на 443 фактически отвечает не TLS-сервис.
Быстрая диагностика: с чего начать
Сначала нужно понять, где именно ломается соединение: DNS, TCP, TLS, сертификат, прокси или конкретный клиент.
1) Проверьте, что домен резолвится туда, куда нужно — особенно после переезда, смены CDN или балансировщика:
dig example.com A
dig example.com AAAA
dig www.example.com AЕсли IPv4 и IPv6 ведут на разные инфраструктуры, ошибка может проявляться только у части пользователей. Частый сценарий: A-запись указывает на новый сервер с корректным сертификатом, а AAAA — на старый сервер с просроченным.
2) Проверьте TCP-доступность порта 443 — TLS не начнётся, если соединение не устанавливается:
nc -vz example.com 443Если TCP не проходит, причина не в сертификате, а в маршрутизации, фаерволе, security group, балансировщике или недоступном сервере.
3) Посмотрите сертификат и цепочку через OpenSSL:
openssl s_client -connect example.com:443 -servername example.com -showcertsКлючевой параметр здесь — -servername. Он включает SNI. Без него сервер может отдать сертификат по умолчанию, и диагностика будет ложной.
Смотрите на строки:
Verify return code: 0 (ok)— цепочка корректна;certificate has expired— сертификат истёк;unable to get local issuer certificate— неполная цепочка;self signed certificate— клиент не доверяет сертификату;no peer certificate available— сервер не отдал сертификат.
4) Сравните проверку с разных клиентов — браузер, curl, серверный curl, мобильная сеть, внешний мониторинг. Если ошибка есть только из одной сети, вероятны DPI, корпоративный прокси, фаервол или IPv6-маршрут.
5) Проверьте логи в момент ошибки — у Nginx это обычно /var/log/nginx/error.log, у HAProxy — системный журнал или отдельный лог, у Kubernetes ingress — логи pod’ов ingress-контроллера.
Если сайт важен для бизнеса, такие сбои лучше замечать раньше, чем от пользователей начнут поступать жалобы. Мониторинг с HTTPS-проверкой, например Statuser, регулярно опрашивает сайт и уведомляет, если рукопожатие начинает падать.
Сертификат: срок, домен, цепочка и доверие
Ошибки сертификата — самая понятная, но не единственная причина TLS-сбоев.
1) Истёк срок действия сертификата — браузеры сразу блокируют соединение. Проверяется командой:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -datesЕсли используете Let’s Encrypt, проверьте автообновление, certbot renew, таймеры systemd и перезагрузку Nginx после выпуска нового сертификата. Подробная инструкция есть в статье Как настроить автоматическое обновление сертификатов Let’s Encrypt с помощью certbot.
2) Сертификат выпущен не на тот домен — например, сертификат покрывает example.com, но не покрывает www.example.com, api.example.com или кириллический домен в punycode. Проверяйте Subject Alternative Name, а не только CN:
openssl x509 -in cert.pem -noout -text | grep -A1 "Subject Alternative Name"Современные клиенты ориентируются именно на SAN.
3) Неполная цепочка сертификатов — сервер отдаёт только leaf-сертификат, но не intermediate. У части клиентов всё может работать за счёт кэша промежуточных сертификатов, у других — падать. В Nginx обычно нужно указывать fullchain.pem, а не cert.pem.
Пример корректной настройки:
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;4) Самоподписанный сертификат в продакшене — допустим для локальной разработки, внутренних стендов или закрытых контуров, но публичные браузеры ему не доверяют. Если самоподписанный сертификат нужен осознанно, клиентам придётся добавить ваш CA в доверенные.
5) Неверное системное время — если часы на клиенте или сервере сильно отличаются, сертификат может считаться ещё не действующим или уже истёкшим. На серверах проверьте timedatectl, chrony, systemd-timesyncd.
Для публичных сайтов полезно отдельно следить за сроками сертификатов. Почему это стоит вынести в мониторинг, разобрано в статье Почему важен мониторинг SSL-сертификатов.
SNI, ALPN и виртуальные хосты
На одном IP-адресе часто живут десятки HTTPS-сайтов. Чтобы сервер понял, какой сертификат отдать, клиент передаёт имя домена через расширение SNI.
1) Клиент не отправляет SNI — старые клиенты, старые библиотеки, некоторые встроенные устройства и неправильно написанные health checks могут подключаться к IP:443 без имени домена. Сервер в таком случае отдаёт сертификат виртуального хоста по умолчанию. Для пользователя это выглядит как ошибка имени сертификата.
Проверка с SNI:
openssl s_client -connect 203.0.113.10:443 -servername example.comПроверка без SNI:
openssl s_client -connect 203.0.113.10:443Если результаты отличаются, проблема связана с выбором виртуального хоста.
2) Неправильный server_name в Nginx — например, настроен только example.com, а пользователи заходят на www.example.com. В таком случае запрос может попадать в дефолтный серверный блок.
Минимальный пример:
server {
listen 443 ssl;
http2 on;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}Базовая настройка HTTPS в Nginx подробно разобрана здесь: Как настроить HTTPS и бесплатный SSL-сертификат в Nginx.
3) Ошибки ALPN — через ALPN клиент и сервер договариваются, использовать HTTP/2, HTTP/1.1 или другой протокол. Сам по себе неверный ALPN редко ломает TLS полностью, но может вызывать проблемы на связке CDN → балансировщик → backend, особенно если один слой ожидает h2, а другой работает только с http/1.1.
4) Балансировщик терминирует TLS не там, где вы думаете — сертификат может быть корректным на Nginx, но трафик до него не доходит: TLS завершается на CDN, ingress, HAProxy, cloud load balancer или API Gateway. Проверять нужно тот слой, который реально принимает HTTPS от клиента.
Если хотите глубже разобраться, как браузер выбирает сертификат и протокол, см. статью SNI и ALPN: как браузер выбирает сертификат и протокол.
Версии TLS и наборы шифров
TLS-рукопожатие требует совместимости. Клиент предлагает версии и шифры, сервер выбирает подходящие. Если пересечения нет, соединение падает.
1) Сервер поддерживает только старые версии TLS — TLS 1.0 и TLS 1.1 давно считаются устаревшими. Современные браузеры и многие API-клиенты их блокируют. Если на старом сервере включён только TLS 1.0, пользователь увидит ошибку протокола.
2) Сервер отключил всё, кроме слишком нового набора — обратная ситуация: включили только TLS 1.3, а часть клиентов работает на старых ОС, Java, OpenSSL или embedded-устройствах. Для публичного сайта обычно разумный минимум — TLS 1.2 и TLS 1.3.
Пример для Nginx:
ssl_protocols TLSv1.2 TLSv1.3;3) Нет общих cipher suites — в логах Nginx это часто выглядит как no shared cipher. Причина может быть в слишком жёсткой настройке ssl_ciphers или старом клиенте.
Проверить конкретную версию TLS можно так:
openssl s_client -connect example.com:443 -servername example.com -tls1_2
openssl s_client -connect example.com:443 -servername example.com -tls1_34) Старые клиенты не поддерживают ECDSA-сертификаты — если сайт использует только ECDSA-сертификат, очень старые клиенты могут не подключиться. Для массовой совместимости иногда используют RSA + ECDSA одновременно, если это поддерживает сервер и инфраструктура.
5) FIPS, корпоративные политики и кастомные trust store — в enterprise-средах клиенты могут запрещать часть алгоритмов или доверять только внутренним CA. Тогда сайт работает у обычных пользователей, но падает у клиентов из корпоративной сети.
Не стоит «чинить» это включением всего подряд. Возвращать TLS 1.0, слабые шифры и небезопасные алгоритмы ради одного клиента — плохой компромисс. Лучше выяснить, какой клиент подключается, можно ли обновить его библиотеку TLS и какие требования у вашей аудитории.
Сетевые и инфраструктурные причины
Иногда сертификат и TLS-настройки корректны, но рукопожатие всё равно обрывается. Тогда нужно смотреть сеть и промежуточные слои.
1) На порту 443 отвечает не HTTPS — классика после неправильного деплоя или изменения port mapping в Docker. Клиент начинает TLS, а сервер отвечает обычным HTTP или вообще другим протоколом. В curl может появиться wrong version number.
Проверьте, кто слушает порт:
ss -ltnp | grep ':443'2) Фаервол или security group пропускает TCP, но режет часть соединений — соединение может устанавливаться, но сбрасываться во время обмена. Ищите RST, дропы, правила DPI, лимиты state table.
3) Проблемы MTU и фрагментации — TLS-пакеты с сертификатом могут быть крупнее, чем первые TCP-пакеты. Если где-то по пути сломана Path MTU Discovery, маленькие проверки проходят, а рукопожатие зависает или обрывается. Это чаще проявляется через VPN, GRE/IPsec-туннели, облачные сети, Kubernetes overlay.
4) Перегружен балансировщик или Nginx — при нехватке worker’ов, файловых дескрипторов, CPU или переполнении очередей соединения могут закрываться до завершения TLS. В логах это выглядит не как «красивый» отказ сертификата, а как таймауты, connection reset, SSL_ERROR_SYSCALL.
5) CDN или WAF меняет поведение — у CDN обычно есть два TLS-сегмента: клиент → CDN и CDN → origin. Ошибка может быть на любом из них. Например, браузер успешно подключается к CDN, но CDN не может установить TLS к вашему origin из-за просроченного сертификата или несовпадения имени.
6) IPv6 настроен хуже IPv4 — пользователи с IPv6 получают один результат, остальные — другой. Проверяйте отдельно:
curl -4 -Iv https://example.com
curl -6 -Iv https://example.comЕсли -4 работает, а -6 нет, ищите проблему в AAAA-записи, IPv6-маршруте, security group или сертификате на другом endpoint.
Как исправить ошибку TLS-рукопожатия: практический чеклист
Лучше идти от простого к сложному и фиксировать результат каждой проверки.
1) Убедитесь, что проверяете правильный домен — example.com, www.example.com, api.example.com и wildcard-сертификаты — разные вещи. Проверьте также редиректы: пользователь может начинать с одного имени, а после 301 попадать на другое.
2) Проверьте DNS и IP-адреса — сравните A, AAAA, записи CDN и балансировщика. Если недавно был переезд, часть пользователей может ходить на старый адрес из-за DNS-кэша.
3) Проверьте сертификат через openssl s_client с -servername — это быстро покажет срок, цепочку и имя сертификата. Если Verify return code не 0, начните с сертификатов.
4) Исправьте цепочку — для Nginx используйте fullchain.pem, для HAProxy — PEM-файл, где сертификат и цепочка собраны корректно. После замены перезагрузите сервис:
nginx -t && systemctl reload nginx5) Проверьте виртуальные хосты и SNI — убедитесь, что нужный домен есть в server_name, а дефолтный server не отдаёт чужой сертификат. Для нескольких сайтов на одном IP это критично.
6) Настройте совместимые версии TLS — включите TLS 1.2 и TLS 1.3, если нет специальных ограничений. Не включайте устаревшие протоколы без крайней необходимости.
7) Проверьте CDN, WAF и балансировщик — где именно терминируется TLS? Какой сертификат установлен на публичном endpoint? Как CDN проверяет origin: по IP, по hostname, с каким SNI?
8) Сравните IPv4 и IPv6 — отдельная проверка curl -4 и curl -6 часто экономит часы.
9) Посмотрите логи в момент проверки — если клиент видит handshake failure, а на сервере нет записей, запрос может не доходить до нужного сервиса. Если записи есть, они обычно подскажут: no shared cipher, bad key share, unknown ca, unsupported protocol.
10) Проверьте после исправления из внешней сети — локальная проверка с самого сервера не заменяет проверку снаружи. Используйте другой провайдер, мобильный интернет или внешний мониторинг вроде Statuser, чтобы убедиться, что HTTPS-проверка проходит и у реальных пользователей.
Как предотвратить повторение проблемы
TLS-сбои часто появляются не из-за сложной криптографии, а из-за операционных мелочей: забыли обновить сертификат, поменяли ingress, добавили AAAA, включили CDN, не проверили www.
1) Автоматизируйте выпуск и обновление сертификатов — ручное продление рано или поздно приводит к простою. Для Let’s Encrypt проверьте, что автообновление не только выпускает сертификат, но и перезагружает сервис, который его использует.
2) Держите TLS-настройки в конфигурации и проверяйте изменения — ssl_protocols, ssl_ciphers, server_name, конфиги ingress и балансировщиков должны проходить review так же, как код приложения.
3) Мониторьте не только HTTP-статус, но и срок сертификата — отдельная проверка сертификата и TLS-доступности сигнализирует о проблеме раньше, чем обычный GET /.
4) Проверяйте все публичные имена — основной домен, www, API, админку, статические поддомены, wildcard и не-wildcard варианты. Ошибка часто живёт не на главной странице, а на вторичном поддомене.
5) Не забывайте про IPv6 — если публикуете AAAA-запись, она должна вести на такой же рабочий endpoint, как IPv4: с тем же сертификатом, firewall и маршрутизацией.
6) Тестируйте изменения снаружи перед выкладкой — особенно при замене CDN, ingress-контроллера, HAProxy, Nginx-конфигов или сертификатов. curl localhost не проверяет реальный путь клиент → интернет → CDN → балансировщик → origin.
TLS — часть доступности сайта, а не только безопасности. Если рукопожатие не проходит, пользователь не увидит даже страницу ошибки приложения. Поэтому TLS-проверки стоит включать в общий контур мониторинга вместе с HTTP-статусами, временем ответа и алертами.
FAQ
Что значит ошибка TLS-рукопожатия простыми словами?
Клиент и сервер не смогли договориться о защищённом HTTPS-соединении. Причина может быть в сертификате, версиях TLS, шифрах, SNI, прокси, CDN или сети.
Почему сайт открывается у меня, но не открывается у других?
Частые причины: разные DNS-кэши, IPv4/IPv6, корпоративный прокси, старый браузер, старый trust store, CDN с региональными endpoint’ами или неполная цепочка сертификатов.
Можно ли исправить ошибку, просто перевыпустив сертификат?
Иногда да, если сертификат истёк, выпущен не на тот домен или собран без intermediate-цепочки. Но если проблема в SNI, шифрах, TLS-версии или балансировщике, перевыпуск не поможет.
Почему openssl s_client показывает один сертификат, а браузер — другой?
Скорее всего, проверка выполнялась без SNI или через другой IP-адрес. Используйте -servername example.com и отдельно проверяйте A и AAAA-записи.
Похожие статьи

Ошибка 500 Internal Server Error: что означает и как исправить
Практическое руководство по диагностике HTTP 500: причины, логи, проверки, исправления и профилактика повторных сбоев.
5 августа 20269 мин

ERR_CONNECTION_REFUSED: что за ошибка и как исправить
Разбираем причины отказа соединения в браузере и пошаговую диагностику для пользователя, разработчика и администратора сайта.
10 августа 202610 мин

TLS handshake под микроскопом: разбор по шагам с tcpdump
Детальный разбор TLS-рукопожатия: что происходит между клиентом и сервером, как читать ClientHello и ServerHello и как диагностировать ошибки HTTPS.
16 февраля 20267 мин
Настроить мониторинг за 30 секунд
Надежные оповещения о даунтаймах. Без ложных срабатываний