lenec ru

← все посты

SSL: SSLV3_ALERT_HANDSHAKE_FAILURE — как чинить

19K

Эта ошибка приходит в самых разных местах: из curl, из Node-кода через fetch/https, из Python-скриптов. Где бы ни всплыла, текст один:

SSL routines:ssl3_read_bytes:sslv3 alert handshake failure

Несмотря на упоминание SSLv3, к самому SSLv3 это обычно отношения не имеет. Это TLS-сообщение «alert: handshake failure» — сервер на той стороне отказался устанавливать соединение по причинам, которые ему не понравились. Дальше нужно разобраться, по каким именно.

В двух словах: что произошло

TLS-рукопожатие — это переговорный процесс между клиентом и сервером. Они договариваются:

  • о версии TLS (1.2, 1.3);
  • о наборе шифров;
  • о том, какой клиент и сервер существует (SNI, ALPN);
  • о клиентских сертификатах, если сервер их требует.

Если в чём-то одном не сошлись — сервер отдаёт alert: handshake failure и обрывает соединение. Клиент видит ту самую ошибку.

Самый быстрый дебаг — openssl s_client

Прежде чем ковырять Node-приложение, я смотрю, как ведёт себя openssl:

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

Этот вывод — золото. В нём видно:

  • версию TLS, по которой реально установилось соединение;
  • какой шифр выбран;
  • какой сертификат сервер показал.

Если соединение даже здесь не устанавливается — проблема не в твоём коде, а в инфраструктуре.

Сценарий 1: устаревший клиент

Самый частый случай. Сервер требует TLS 1.2 минимум (или 1.3), а старый Node 10 / старый Python / старый OpenSSL 1.0.x с ним не договорится.

Проверка:

openssl version
node -v

Если OpenSSL 1.0.x или 1.1.0 — это глубоко устаревший набор. Современные сервера часто отключают всё, что ниже TLS 1.2 и нестандартные шифры.

Лекарство — обновить Node. На современном Node 20+ OpenSSL 3.x, и проблем со «старыми клиент vs новый сервер» практически не бывает.

Сценарий 2: сервер требует определённый шифр или TLS-версию

Видел в банковских интеграциях: сервер настолько строгий, что принимает один-два конкретных шифра. Если в клиенте они отключены — handshake failure.

Проверить, что сервер принимает:

openssl s_client -connect api.example.com:443 -tls1_2 -cipher 'ECDHE-RSA-AES128-GCM-SHA256'

Перебираешь шифры по списку и смотришь, на каком соединение устанавливается. В Node, если очень нужно, можно задать конкретные:

import https from 'https';

const agent = new https.Agent({
  ciphers: 'ECDHE-RSA-AES128-GCM-SHA256',
  minVersion: 'TLSv1.2',
});

const res = await fetch('https://api.example.com/x', { agent });

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

Сценарий 3: SNI

Server Name Indication — это поле, в котором клиент сразу в начале handshake говорит «я хочу к домену example.com». Современные сервера на одном IP держат сертификаты для десятков доменов и определяют, какой отдать, по SNI.

Если клиент SNI не отправил, сервер не понимает, какой сертификат показать, и обрывает.

В curl SNI отправляется автоматически. В openssl s_client нужен -servername:

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

В Node — то же самое: если ты подключаешься к IP, а не к домену, и не задаёшь servername, можешь получить handshake failure.

import { connect } from 'tls';

const socket = connect({
  host: '203.0.113.10',
  port: 443,
  servername: 'api.example.com',
});

Сценарий 4: клиентский сертификат

Иногда сервер требует mTLS — клиент должен предъявить сертификат. Если клиент его не отправил, сервер шлёт handshake failure.

В curl это:

curl --cert ./client.pem --key ./client.key https://api.example.com/x

В Node:

import https from 'https';
import { readFileSync } from 'fs';

const agent = new https.Agent({
  cert: readFileSync('./client.pem'),
  key: readFileSync('./client.key'),
});

await fetch('https://api.example.com/x', { agent });

В банковских и B2B-интеграциях этот сценарий регулярно встречаю.

Сценарий 5: цепочка сертификатов на сервере неполная

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

Проверка:

openssl s_client -connect api.example.com:443 -showcerts

Смотришь количество сертификатов в выводе. Должен быть листовой + один-два промежуточных. Если только листовой — сервер настроен неполностью, проблема на стороне сервера.

Иногда временный фикс на стороне клиента — добавить промежуточные сертификаты в trust store. Но это симптом, и нормальное лечение — попросить владельцев сервера дозалить fullchain.

Сценарий 6: ALPN не сошёлся

HTTP/2 требует ALPN-согласования. Если клиент шлёт ALPN, в котором есть только h2, а сервер не поддерживает HTTP/2 и не может предложить http/1.1 в ответ — рассогласование, alert.

В практике у меня это всплывает, когда подключаюсь к старому самописному прокси. Лечится либо обновлением прокси, либо явным запретом HTTP/2 на клиенте:

const agent = new https.Agent({ ALPNProtocols: ['http/1.1'] });

Алгоритм, которым я пользуюсь

  • Запустить openssl s_client и посмотреть, устанавливается ли вообще TLS.
  • Если нет — проверить версию OpenSSL и Node, обновить, если устарели.
  • Если да через openssl, но не через мой код — сравнить версии TLS, шифры, SNI.
  • Если сервер требует mTLS — добавить клиентский сертификат.
  • Если у сервера неполная цепочка — попросить починить, потому что это его проблема.

Чего избегать

Не отключать проверку сертификатов как «фикс». NODE_TLS_REJECT_UNAUTHORIZED=0 или rejectUnauthorized: false в Node убирает не то предупреждение, что нужно. Handshake failure — это про этап ниже, до проверки сертификата клиентом.

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

Не игнорировать ошибку. Если соединение успешно отвалилось на handshake — значит обе стороны не договорились, и втихаря работать оно не начнёт.

SSLV3_ALERT_HANDSHAKE_FAILURE — это сообщение, не диагноз. Для диагноза нужно посмотреть на сторону сервера, версию TLS, шифры и SNI. Десять минут с openssl s_client обычно решают всё.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.