sslv3 alert handshake failure. 어제까지 되던 API 호출이 갑자기 이 에러로 막히면 대부분 TLS 버전이나 cipher 협상 실패입니다. 클라이언트와 서버가 서로 받아들일 수 있는 공통의 프로토콜 버전과 암호군(cipher suite)을 찾지 못하면, 핸드셰이크는 데이터 한 바이트 오가기 전에 끊깁니다.
핸드셰이크에서 무엇을 합의하나
TLS 핸드셰이크 첫 단계에서 클라이언트는 "내가 지원하는 버전·cipher 목록"을 보내고, 서버가 그중 하나를 고릅니다. 교집합이 비어 있으면 협상 실패입니다. 흔한 원인은 서버가 구버전 TLS(1.0/1.1)를 막았는데 클라이언트가 신버전을 못 쓰거나, 반대로 cipher 정책이 어긋난 경우입니다.
openssl로 버전을 하나씩 끊어본다
어느 버전에서 되고 안 되는지부터 가립니다.
openssl s_client -connect api.example.com:443 -tls1_2
openssl s_client -connect api.example.com:443 -tls1_3
성공하면 인증서 체인과 Cipher is ... 가 출력되고, 실패하면 다음처럼 끊깁니다.
140735...:error:14094410:SSL routines:ssl3_read_bytes:
sslv3 alert handshake failure:ssl/record/rec_layer_s3.c:1543:
SSL alert number 40
-tls1_2 는 되는데 -tls1_3 만 실패한다면 클라이언트의 OpenSSL이 1.3을 모르는 것이고, 반대면 서버가 구버전을 닫은 것입니다. SNI가 필요한 환경에서는 -servername api.example.com 도 함께 붙입니다.
cipher를 지정해 교집합을 찾는다
특정 cipher만 거부된다면 직접 지정해 확인합니다.
# 서버가 어떤 cipher를 받아주는지
openssl s_client -connect api.example.com:443 \
-tls1_2 -cipher 'ECDHE-RSA-AES128-GCM-SHA256'
New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
이 줄이 나오면 협상 성공입니다. 지정한 cipher가 모두 거부되면 handshake failure 가 나는데, 이는 서버 cipher 정책과 클라이언트 라이브러리가 겹치는 게 없다는 뜻입니다.
증상별 원인
| 상황 | 의미 | 1순위 조치 |
|---|---|---|
| 모든 버전 실패 | 포트·SNI·인증서 문제일 수도 | -servername 추가, 포트 재확인 |
-tls1_3 만 실패 | 한쪽이 1.3 미지원 | OpenSSL/라이브러리 버전 업 |
-tls1_0/1_1 만 실패 | 서버가 구버전 폐기 | 클라이언트를 1.2 이상으로 |
| 특정 cipher만 실패 | cipher 정책 불일치 | 공통 cipher로 맞추기 |
점검 체크리스트
# 1) 버전별로 끊어보기
openssl s_client -connect HOST:443 -tls1_2 -servername HOST
openssl s_client -connect HOST:443 -tls1_3 -servername HOST
# 2) cipher 지정해 교집합 찾기
openssl s_client -connect HOST:443 -tls1_2 -cipher 'ECDHE-RSA-AES128-GCM-SHA256'
# 3) 클라이언트가 가진 버전·cipher 확인
openssl version
openssl ciphers -v | head
레거시 라이브러리(오래된 Java, Python, curl)가 신버전 TLS만 여는 서버에 붙을 때 이 에러가 잦습니다. 양쪽이 지원하는 버전·cipher의 교집합을 찾는 것이 진단의 전부입니다.
TLS 버전·cipher 협상을 openssl s_client 로 직접 끊어보며 진단하는 실습은 네트워크 트랙에서 회원가입 없이 무료로 할 수 있습니다.