[태그:] TLS 연결 오류

  • 서버에서 TLS Handshake Failed 오류가 발생하는 원인과 확인 방법

    서버에서 TLS Handshake Failed 오류가 발생하는 원인과 확인 방법

    웹사이트에 HTTPS로 접속하거나 서버에서 외부 API를 호출하는 과정에서 TLS Handshake Failed 오류가 발생하는 경우가 있습니다. 웹브라우저에서는 보안 연결을 설정할 수 없다는 메시지가 나타나고, 서버 로그에는 handshake failure, protocol version, certificate verify failed 같은 오류가 기록되기도 합니다.

    이런 문제가 발생하면 SSL 인증서가 만료됐다고 생각하기 쉽지만, 실제로는 TLS 버전 불일치, 암호화 알고리즘 충돌, SNI 설정 오류, 인증서 체인 문제, 프록시 설정 또는 네트워크 연결 문제 등 여러 원인이 영향을 줄 수 있습니다.

    특히 브라우저에서는 정상적으로 접속되는데 특정 프로그램이나 서버에서만 TLS 연결이 실패한다면, 인증서 자체보다 클라이언트의 TLS 지원 범위와 인증서 검증 환경을 함께 살펴봐야 합니다.

    이번 글에서는 오류 메시지 확인 → 인증서와 TLS 버전 검사 → 암호화 알고리즘 및 SNI 확인 → 서버 로그 분석 → 프록시와 네트워크 점검 순서로 원인을 좁히는 방법을 설명합니다.

    TLS Handshake Failed 핵심 명령어 한눈에 보기

    TLS 오류를 확인할 때는 서버의 인증서뿐 아니라 클라이언트와 서버가 실제로 어떤 TLS 조건으로 연결을 시도하는지 살펴봐야 합니다. 다음 예시는 example.com의 HTTPS 443번 포트를 기준으로 작성했습니다.

    점검 목적명령어
    HTTPS 연결 상세 확인 curl -Iv https://example.com/
    TLS 연결 및 인증서 확인 openssl s_client -connect example.com:443 -servername example.com
    TLS 1.2 연결 확인 openssl s_client -connect example.com:443 -servername example.com -tls1_2
    TLS 1.3 연결 확인 openssl s_client -connect example.com:443 -servername example.com -tls1_3
    인증서 날짜 확인 openssl x509 -in server.crt -noout -dates
    Nginx 설정 검사 sudo nginx -t
    Nginx 오류 로그 sudo tail -n 100 /var/log/nginx/error.log
    443번 포트 확인 sudo ss -lntp 'sport = :443'

    가장 먼저 확인할 것은 오류가 TLS 협상 중 발생했는지, 인증서 검증 단계에서 발생했는지 구분하는 것입니다. 두 문제 모두 보안 연결 실패로 나타날 수 있지만 해결 방법은 다릅니다.

    서버 TLS Handshake Failed 오류와 OpenSSL curl 명령어를 이용한 SSL TLS 연결 점검
    TLS Handshake Failed 오류 발생 시 인증서, TLS 버전, SNI 및 서버 연결 상태를 점검하는 방법

    1. TLS Handshake Failed 오류가 발생하는 이유

    TLS(Transport Layer Security)는 클라이언트와 서버가 통신 내용을 암호화하고, 서버 신원을 검증할 수 있도록 지원하는 보안 프로토콜입니다.

    HTTPS 연결을 시작하면 클라이언트와 서버는 지원하는 TLS 버전과 암호화 알고리즘을 협상하고, 필요한 인증서 검증과 키 교환 절차를 수행합니다.

    이 과정이 정상적으로 완료되지 않으면 TLS Handshake Failed 또는 관련 오류가 발생할 수 있습니다.

    대표적인 원인은 다음과 같습니다.

    • TLS 버전 불일치: 클라이언트와 서버가 공통으로 지원하는 TLS 버전이 없는 경우
    • 암호화 알고리즘 불일치: 공통으로 사용할 수 있는 Cipher Suite 또는 관련 암호화 조건이 없는 경우
    • 인증서 검증 실패: 인증서 만료, 신뢰할 수 없는 발급기관, 인증서 체인 누락 등이 발생한 경우
    • SNI 설정 문제: 요청한 도메인에 맞는 인증서나 TLS 가상 호스트가 선택되지 않는 경우
    • 서버 설정 오류: Nginx, Apache, 로드밸런서의 TLS 설정이 잘못된 경우
    • 클라이언트 환경 문제: 오래된 OpenSSL, Java 또는 시스템 신뢰 저장소를 사용하는 경우
    • 네트워크 및 프록시 문제: 연결 중단, TLS 검사 장비, 잘못된 프록시 라우팅 등이 발생하는 경우
    • 상호 TLS 인증 문제: 서버가 요구하는 클라이언트 인증서가 없거나 유효하지 않은 경우

    다만 모든 인증서 검증 오류가 TLS 프로토콜의 핸드셰이크 실패로 동일하게 기록되는 것은 아닙니다. 클라이언트 프로그램에 따라 TLS 연결 실패와 인증서 검증 실패를 서로 다른 메시지로 표시할 수 있습니다.

    2. curl로 HTTPS 연결 오류 확인하기

    가장 먼저 curl을 이용해 HTTPS 연결 과정을 확인합니다.

    curl -Iv https://example.com/

    -I는 HTTP 헤더 요청, -v는 연결 과정을 자세히 출력하는 옵션입니다.

    예를 들어 TLS 버전이 맞지 않는 환경에서는 다음과 비슷한 오류가 발생할 수 있습니다.

    curl: (35) OpenSSL/SSL_connect:
    SSL_ERROR_SYSCALL

    다만 위 메시지만으로 TLS 버전 불일치를 확정할 수는 없습니다. 연결이 비정상적으로 종료되거나 네트워크 장비가 연결을 차단한 경우에도 유사한 메시지가 나타날 수 있습니다.

    인증서 검증 실패라면 다음과 같은 오류가 나타날 수 있습니다.

    curl: (60) SSL certificate problem:
    unable to get local issuer certificate

    이 경우에는 서버의 인증서 체인과 클라이언트의 신뢰할 수 있는 CA 인증서 목록을 확인해야 합니다.

    원인을 확인하기 위해 curl -k로 인증서 검증을 비활성화하는 방법은 일시적인 진단 용도로만 제한해야 합니다. 운영 프로그램에서 인증서 검증을 끄는 것은 중간자 공격 위험을 높일 수 있습니다.

    3. OpenSSL로 TLS 버전과 인증서 확인하기

    OpenSSL의 s_client 명령어를 사용하면 서버가 제공하는 인증서와 협상된 TLS 버전, 암호화 알고리즘 등을 확인할 수 있습니다.

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

    여기서 -servername 옵션은 SNI(Server Name Indication)를 지정합니다. 여러 도메인을 하나의 IP 주소에서 서비스하는 환경에서는 올바른 인증서를 선택하는 데 중요합니다.

    연결에 성공하면 다음과 같은 정보가 표시될 수 있습니다.

    New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
    Server public key is 2048 bit
    Verify return code: 0 (ok)

    이는 예시 출력이며 실제 TLS 버전과 암호화 알고리즘은 서버 및 클라이언트 설정에 따라 달라집니다.

    다만 Verify return code: 0 (ok)만으로 접속 도메인과 인증서 이름이 일치한다고 단정해서는 안 됩니다. 도메인 이름까지 검증하려면 다음 명령어를 사용할 수 있습니다.

    openssl s_client \
      -connect example.com:443 \
      -servername example.com \
      -verify_hostname example.com \
      -verify_return_error

    이 명령어는 인증서 검증 오류를 확인하는 데 도움이 됩니다. 단, OpenSSL 버전과 신뢰 저장소 설정에 따라 결과가 달라질 수 있습니다.

    4. TLS 1.2와 TLS 1.3 버전 불일치 확인하기

    클라이언트와 서버가 공통으로 지원하는 TLS 버전이 없다면 핸드셰이크가 실패할 수 있습니다.

    TLS 1.2 연결을 확인합니다.

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

    TLS 1.3 연결도 별도로 확인합니다.

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

    예를 들어 TLS 1.3 연결은 성공하지만 TLS 1.2 연결이 실패한다면 서버가 TLS 1.2를 지원하지 않거나 해당 버전에서 사용할 수 있는 암호화 조건이 맞지 않을 가능성이 있습니다.

    반대로 특정 클라이언트만 실패한다면 그 클라이언트의 OpenSSL 또는 TLS 라이브러리 버전도 확인해야 합니다.

    openssl version -a
    curl -V

    현재 운영 환경에서는 일반적으로 TLS 1.2와 TLS 1.3을 중심으로 호환성과 보안 요구사항을 검토하는 것이 좋습니다.

    오래된 클라이언트를 지원하기 위해 TLS 1.0이나 TLS 1.1을 무조건 활성화하는 것은 보안상 권장되지 않습니다.

    5. SSL 인증서 만료와 인증서 체인 문제 확인하기

    TLS 연결 문제 중에는 인증서 자체의 유효성이나 인증서 체인 구성에 문제가 있는 경우도 있습니다.

    로컬 인증서 파일의 유효기간은 다음 명령어로 확인할 수 있습니다.

    openssl x509 \
      -in server.crt \
      -noout -dates

    출력 예시는 다음과 같습니다.

    notBefore=Oct  1 00:00:00 2026 GMT
    notAfter=Dec 30 23:59:59 2026 GMT

    notBefore는 인증서 유효기간 시작, notAfter는 만료 시각입니다.

    다만 서버에 저장된 인증서 파일과 실제로 외부 클라이언트에 제공되는 인증서가 다를 수 있습니다.

    따라서 외부에서 다음 명령어로 실제 서버가 제공하는 인증서도 확인해야 합니다.

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

    인증서 체인이 누락된 경우에는 브라우저에서는 접속되더라도 일부 API 클라이언트나 서버 환경에서 인증서 검증에 실패할 수 있습니다.

    Nginx에서는 일반적으로 서버 인증서와 필요한 중간 인증서를 포함하는 전체 인증서 체인 파일을 ssl_certificate에 지정합니다.

    인증서를 교체할 때는 도메인 이름, 유효기간, 중간 인증서 포함 여부를 함께 확인해야 합니다.

    6. SNI 설정 오류와 도메인별 인증서 문제

    하나의 서버 IP 주소에서 여러 HTTPS 도메인을 운영한다면 SNI 설정이 중요합니다.

    SNI는 클라이언트가 TLS 연결을 시작하면서 접속하려는 서버 이름을 전달하는 기능입니다.

    예를 들어 같은 IP 주소에서 example.com과 api.example.com을 함께 운영한다고 가정하겠습니다.

    서버 이름을 지정하지 않거나 잘못된 이름을 전달하면 기본 가상 호스트의 인증서가 선택될 수 있습니다.

    다음 명령어로 정확한 도메인을 지정해 확인합니다.

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

    Nginx에서는 도메인별 서버 설정을 확인해야 합니다.

    server {
        listen 443 ssl;
        server_name example.com;
    
        ssl_certificate /etc/nginx/ssl/fullchain.pem;
        ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    }

    위 코드는 설정 구조를 보여주는 예시입니다. 실제 인증서 경로와 도메인은 서버 환경에 맞게 변경해야 합니다.

    또한 SNI가 올바르더라도 인증서의 SAN 항목에 요청한 도메인이 포함되어 있지 않으면 클라이언트의 인증서 이름 검증이 실패할 수 있습니다.

    7. Nginx 로그로 TLS 핸드셰이크 실패 원인 확인하기

    Nginx를 사용하는 서버라면 오류 로그에서 TLS 연결 실패 메시지를 확인할 수 있습니다.

    sudo tail -n 100 /var/log/nginx/error.log

    다음과 같은 로그가 기록될 수 있습니다.

    SSL_do_handshake() failed
    (SSL: error:0A000102:SSL routines::unsupported protocol)

    이 메시지는 지원하지 않는 TLS 프로토콜과 관련된 문제를 나타낼 수 있습니다.

    또 다른 예시는 다음과 같습니다.

    SSL_do_handshake() failed
    (SSL: error:0A0000C1:SSL routines::no shared cipher)

    이 경우에는 클라이언트와 서버 사이에 공통으로 사용할 수 있는 암호화 조건이 없는지 확인해야 합니다.

    다만 오류 코드와 로그 문구는 OpenSSL 및 Nginx 버전에 따라 다를 수 있습니다.

    Nginx 설정을 변경하기 전에는 다음 명령어로 현재 설정 문법을 검사해야 합니다.

    sudo nginx -t

    설정이 정상이고 변경 내용을 적용해야 한다면 운영 환경의 변경 절차에 따라 재로드할 수 있습니다.

    sudo systemctl reload nginx

    TLS 오류가 발생했다는 이유만으로 암호화 알고리즘을 광범위하게 허용하거나 보안 수준을 낮추는 것은 피해야 합니다.

    8. 프록시와 로드밸런서에서 발생하는 TLS 오류

    웹사이트 앞에 CDN, 리버스 프록시, 로드밸런서가 있다면 TLS 연결이 여러 구간에서 이루어질 수 있습니다.

    예를 들어 다음과 같은 구조를 생각할 수 있습니다.

    Client
       |
       | HTTPS
       v
    Load Balancer
       |
       | HTTPS
       v
    Nginx
       |
       | HTTP 또는 HTTPS
       v
    Application

    이 경우 클라이언트와 로드밸런서 사이의 TLS 연결은 성공하지만 로드밸런서와 Nginx 사이에서 핸드셰이크가 실패할 수 있습니다.

    따라서 외부 HTTPS 연결이 정상이라고 해서 내부 TLS 연결까지 정상이라고 판단해서는 안 됩니다.

    확인해야 할 항목은 다음과 같습니다.

    • 로드밸런서와 백엔드 서버의 TLS 버전 호환성
    • 백엔드 인증서의 도메인 이름과 신뢰 체인
    • 프록시가 전달하는 SNI 값
    • HTTPS 백엔드 포트와 프로토콜 설정
    • 상호 TLS 인증서 요구 여부
    • CDN과 원본 서버의 인증서 설정

    특히 HTTPS 백엔드에 HTTP 방식으로 연결하거나 반대로 HTTP 포트에 TLS 연결을 시도하면 프로토콜 불일치 오류가 발생할 수 있습니다.

    9. TLS Handshake Failed 오류가 특정 클라이언트에서만 발생하는 경우

    서버 자체는 정상인데 특정 프로그램이나 운영체제에서만 TLS 연결에 실패한다면 클라이언트 환경을 확인해야 합니다.

    대표적으로 오래된 Java 런타임이나 OpenSSL 라이브러리가 서버에서 허용하는 TLS 조건을 지원하지 않을 수 있습니다.

    또한 시스템의 CA 인증서 저장소가 오래됐다면 정상적인 서버 인증서도 신뢰하지 못할 수 있습니다.

    다음 명령어로 기본적인 클라이언트 환경을 확인할 수 있습니다.

    openssl version -a
    curl -V
    date -u

    시스템 시간이 크게 잘못 설정된 경우에도 인증서의 유효기간 검증이 실패할 수 있으므로 시간 동기화 상태를 확인해야 합니다.

    클라이언트 문제를 해결할 때는 인증서 검증을 비활성화하기보다 신뢰 저장소와 TLS 라이브러리를 정상적으로 업데이트하는 것이 좋습니다.

    실전 점검 순서

    TLS Handshake Failed 오류가 발생했다면 다음 순서로 점검할 수 있습니다.

    # 1. HTTPS 연결 상태 확인
    curl -Iv https://example.com/
    
    # 2. TLS 연결 및 인증서 확인
    openssl s_client \
      -connect example.com:443 \
      -servername example.com \
      -verify_hostname example.com \
      -verify_return_error
    
    # 3. TLS 1.2 연결 확인
    openssl s_client \
      -connect example.com:443 \
      -servername example.com \
      -tls1_2
    
    # 4. TLS 1.3 연결 확인
    openssl s_client \
      -connect example.com:443 \
      -servername example.com \
      -tls1_3
    
    # 5. 클라이언트 TLS 환경 확인
    openssl version -a
    curl -V
    
    # 6. 서버의 443번 포트 확인
    sudo ss -lntp 'sport = :443'
    
    # 7. Nginx 설정 검사
    sudo nginx -t
    
    # 8. Nginx 오류 로그 확인
    sudo tail -n 100 /var/log/nginx/error.log

    위 명령어에서 example.com은 실제 점검하려는 도메인으로 변경해야 합니다.

    Nginx를 사용하지 않는 서버라면 Apache, HAProxy, Envoy 또는 해당 애플리케이션의 TLS 로그와 설정을 확인해야 합니다.

    핸드셰이크 실패 메시지와 인증서 검증 실패 메시지를 구분하고, 클라이언트와 서버 양쪽의 설정을 비교하는 것이 중요합니다.

    정리

    서버에서 TLS Handshake Failed 오류가 발생하는 원인은 인증서 만료뿐 아니라 TLS 버전 불일치, 암호화 알고리즘 충돌, SNI 설정 오류, 인증서 체인 누락 및 프록시 설정 문제 등 다양합니다.

    먼저 curl -Iv로 연결 실패 메시지를 확인하고, openssl s_client로 TLS 버전과 인증서 검증 상태를 분석해야 합니다.

    서버를 직접 관리하는 환경이라면 Nginx 또는 사용하는 웹서버의 TLS 설정과 오류 로그를 함께 확인하는 것이 좋습니다.

    특히 특정 클라이언트에서만 실패한다면 서버 설정을 무작정 변경하기보다 클라이언트의 TLS 라이브러리, CA 인증서 저장소, SNI 지원 여부를 먼저 점검해야 합니다.

    핵심 점검 순서는 오류 메시지 확인 → TLS 버전 검사 → 인증서 및 SNI 확인 → 서버 로그 분석 → 프록시·네트워크 점검 → 원인별 설정 수정입니다.