UptimeChecker

ERR_SSL_PROTOCOL_ERROR: причины и решение

Обложка: ERR_SSL_PROTOCOL_ERROR: причины и решение

Что такое ERR_SSL_PROTOCOL_ERROR и что он значит

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

Важно понимать разницу между этой ошибкой и другими SSL-проблемами. Если сертификат истёк или подписан не для этого домена, браузер обычно показывает «срок действия сертификата истёк» или «не удалось проверить сертификат» — то есть рукопожатие состоялось, но сертификат не прошёл проверку. ERR_SSL_PROTOCOL_ERROR появляется раньше — на этапе самого рукопожатия, до проверки сертификата. Поэтому причины у неё другие.

Самые частые причины:

  • На порту 443 слушает сервис, который не понимает TLS (обычный HTTP, SSH, почтовый сервер).
  • Сайт вообще не слушает порт 443, а клиент принудительно заходит по HTTPS.
  • Старая версия протокола (SSL 3.0, TLS 1.0), которую современные браузеры отключили.
  • Расхождение между клиентом и сервером по cipher suites или версии TLS.
  • Неправильная конфигурация виртуального хоста, когда под одним IP несколько доменов.
  • Вмешательство антивируса, прокси или устаревший кеш браузера.

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

Диагностика через openssl s_client

Главный инструмент диагностики — openssl s_client. Он вручную выполняет TLS-рукопожатие и показывает, на каком этапе всё ломается. Базовая команда:

openssl s_client -connect example.com:443

Разберём ключевые строки вывода при успешном соединении:

CONNECTED(00000003)
---
Certificate chain
 0 s:CN = example.com
---
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : TLS_AES_256_GCM_SHA384
    ...
---
Verify return code: 0 (ok)
  • CONNECTED(00000003) — TCP-соединение с портом 443 установлено, сервер доступен.
  • Protocol : TLSv1.3 — согласованная версия протокола.
  • Verify return code: 0 (ok) — сертификат прошёл проверку.

Если на каком-то из этапов проблема, вывод будет другим. Например, когда на порту 443 слушает не HTTPS, а что-то другое:

openssl s_client -connect example.com:443
CONNECTED(00000003)
write:errno=104
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 305 bytes

Здесь ключевая строка — SSL handshake has read 0 bytes and written 305 bytes. Клиент отправил данные рукопожатия, но сервер не ответил ни байта. Это классический признак того, что за портом 443 стоит не TLS-сервер, а что-то другое — обычный HTTP, SSH или вообще пустой порт. Ещё один характерный маркер — no peer certificate available: сервер не прислал сертификат, потому что до этого этапа рукопожатие не дошло.

Проверить, что именно отвечает на порту, можно, отправив обычный HTTP-запрос прямо в порт 443:

curl -k https://example.com -v

Если вместо TLS-ответа прилетает HTML или заголовок вроде Server: nginx без шифрования, а в логе curl видно error:1408F10B:SSL routines:ssl3_get_record:wrong version number, значит порт отдаёт не тот протокол.

Причина 1. На порту 443 не TLS-сервер

Самая распространённая причина ошибки ERR_SSL_PROTOCOL_ERROR. Симптомы и решения удобно свести в таблицу.

Симптом в openssl Что происходит на сервере Решение
SSL handshake has read 0 bytes На 443 слушает не TLS (HTTP, SSH, другой сервис) Настроить HTTPS на нужном порту
wrong version number Сервер отвечает на TLS-запрос обычным HTTP-текстом Включить SSL-модуль/директиву listen 443 ssl
no peer certificate available Рукопожатие оборвалось до отправки сертификата Проверить, что за портом именно веб-сервер с TLS
Соединение висит и падает по таймауту Порт 443 закрыт фаерволом, сервер молчит Открыть порт, проверить listen в конфиге

Самый частый сценарий — администратор настроил сайт только на HTTP (порт 80), а браузер или расширение принудительно подставляют HTTPS. В nginx это выглядит как отсутствие блока с listen 443 ssl:

server {
    listen 80;
    server_name example.com;
    # нет listen 443 ssl — HTTPS не работает вовсе
}

Проверить, какие порты слушает nginx, можно так:

ss -tlnp | grep -E ':(80|443)'

Вывод покажет, слушается ли порт 443 и каким процессом. Если его нет — нужно добавить SSL-блок с корректным сертификатом и перезагрузить сервер.

Причина 2. Устаревший протокол TLS

Браузеры последовательно отключают старые протоколы. SSL 2.0 и SSL 3.0 давно выведены из эксплуатации, TLS 1.0 и 1.1 тоже отключены в большинстве современных браузеров. Если сервер настроен только на эти версии, клиент получает ERR_SSL_PROTOCOL_ERROR при попытке соединения.

Проверить, какие версии TLS поддерживает ваш сервер, можно прямым запросом к каждой версии:

openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

Если сервер поддерживает версию, рукопожатие пройдёт и в выводе появится Protocol : TLSv1.2 или TLSv1.3. Если версия отключена, команда завершится с ошибкой вида:

error:1425F102:SSL routines:ssl_choose_client_version:unsupported protocol

Строка unsupported protocol означает, что сервер не принял запрошенную версию. Настроить поддерживаемые версии в nginx можно директивой:

ssl_protocols TLSv1.2 TLSv1.3;

Оставлять TLS 1.0 и 1.1 включёнными не стоит — это не только источник ошибок, но и уязвимость. Современный минимум — TLS 1.2, а лучше только TLS 1.3, если аудитория позволяет.

Причина 3. Расхождение cipher suites

Ещё одна причина сбоя на этапе рукопожатия — несовпадение наборов шифров. Браузер предлагает список поддерживаемых cipher suites, сервер выбирает один из них. Если пересечение пустое, рукопожатие падает.

Проверить, какие шифры предлагает ваш сервер, можно так:

openssl s_client -connect example.com:443 -cipher 'DEFAULT' 2>/dev/null | grep -E 'Cipher|Protocol'

Вывод покажет, какой шифр в итоге согласован. Если сервер возвращает no cipher match или рукопожатие обрывается при принудительном запросе конкретного шифра — пересечение с клиентом пустое.

Частая ошибка настройки — указать в конфиге устаревший или слишком узкий список шифров. В nginx стоит использовать современный набор и не перегружать конфиг ручными списками без необходимости:

ssl_ciphers HIGH:!aNULL:!MD5;

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

Причина 4. SNI и несколько доменов на одном IP

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

Проверить, какой сертификат сервер отдаёт для конкретного домена, можно, явно указав имя через флаг -servername:

openssl s_client -connect example.com:443 -servername example.com

Разница между запросом с -servername и без него — ключ к диагностике. Если с -servername рукопожатие проходит и приходит правильный сертификат, а без — отдаётся чужой, значит конфигурация виртуальных хостов привязана к SNI корректно, но стоит проверить поведение для клиентов, которые SNI не передают (старые браузеры, некоторые API-клиенты).

Обратная ситуация: сервер вообще не настроен на работу с несколькими доменами и отдаёт один сертификат всем. Тогда для «лишнего» домена браузер покажет ошибку — либо про несовпадение имени, либо, в зависимости от конфигурации, про протокол. Лечится это корректной настройкой отдельных server-блоков в nginx с собственными server_name и сертификатами:

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;
}

Каждый домен должен иметь свой блок со своим сертификатом, а сервер должен уметь различать их по SNI.

Причина 5. Клиентские и промежуточные факторы

Иногда ошибка возникает не на сервере, а на пути между браузером и сервером. Это стоит исключить до того, как углубляться в серверную конфигурацию.

  1. Устаревший кеш браузера. Браузер может помнить старую попытку соединения. Очистка кеша или проверка в режиме инкогнито помогает отличить эту причину от реальной.

  2. Антивирус и системный прокси. Некоторые антивирусы перехватывают HTTPS-трафик для проверки и подставляют свой сертификат. Если рукопожатие ломается именно на машине пользователя, временное отключение защиты — быстрый способ проверить.

  3. Системные часы. Если на клиенте сбито время, проверка сертификата даёт ошибку, которая иногда маскируется под проблемы протокола. Но чаще всего сбитые часы дают ошибку именно про сертификат, а не про протокол.

  4. CDN или балансировщик. Если трафик идёт через CDN или балансировщик, рукопожатие может ломаться на их стороне. Стоит проверить, корректно ли там настроен сертификат и какой протокол они предлагают клиентам.

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

Как проверить сайт снаружи

Серверная диагностика через openssl s_client показывает картину с самой машины. Но проблема может быть видна только снаружи — из-за фаервола, CDN или геораспределения. Поэтому после локальных проверок стоит посмотреть на сайт внешним инструментом.

Для быстрой проверки доступности по HTTPS и корректности TLS-настройки подходит проверка SSL-сертификата у UptimeChecker. Она выполняет запрос из внешней точки и показывает, что видит клиент снаружи: устанавливается ли соединение, действителен ли сертификат, корректна ли цепочка. Это удобно для того, чтобы отделить «проблема на сервере» от «проблема в сети конкретного пользователя».

Успешное TLS-рукопожатие в openssl

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

Чек-лист решения ERR_SSL_PROTOCOL_ERROR

Соберём последовательность действий, которая покрывает большинство случаев:

  1. Определить, воспроизводится ли ошибка у всех или только у одного пользователя.
  2. Исключить клиентские факторы: инкогнито, очистка кеша, отключение антивируса, проверка системного времени.
  3. Проверить порт 443: ss -tlnp | grep ':443' — слушается ли он.
  4. Прогнать рукопожатие: openssl s_client -connect example.com:443 — на каком этапе обрыв.
  5. Если handshake has read 0 bytes — на порту не TLS, чинить конфигурацию веб-сервера.
  6. Проверить версии протоколов: openssl s_client -connect example.com:443 -tls1_2.
  7. Проверить шифры: не слишком ли узкий список в конфиге.
  8. Проверить сайт снаружи через внешний инструмент проверки SSL.
  9. После исправления перезагрузить веб-сервер и перепроверить рукопожатие.

Схема TLS-рукопожатия

Отдельно отметим связь с соседней проблемой — истёкшим сертификатом. ERR_SSL_PROTOCOL_ERROR и «срок действия сертификата истёк» — разные ошибки на разных этапах, но их часто путают. Если браузер пишет именно про срок действия, а не про протокол, начните со статьи про истёкший SSL-сертификат — там разобраны первые действия именно для этого случая.

Итог

ERR_SSL_PROTOCOL_ERROR — ошибка этапа рукопожатия, и в большинстве случаев её причина находится на сервере: не тот протокол на порту 443, отключённый TLS или несовпадение шифров. Диагностика через openssl s_client почти всегда показывает точное место обрыва — нужно лишь уметь читать вывод.

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