이 오류가 정확히 말하는 것

크롬 화면에는 이렇게 나옵니다. “사이트에서 보안 연결을 제공할 수 없습니다. example.com에서 지원되지 않는 프로토콜을 사용합니다. ERR_SSL_VERSION_OR_CIPHER_MISMATCH”. 영어 화면이라면 “Unsupported protocol: The client and server don’t support a common SSL protocol version or cipher suite”라는 설명이 붙습니다.

HTTPS의 맨 첫 단계에서 실패했다는 뜻입니다. 브라우저는 연결을 열면서 ClientHello에 자기가 쓸 수 있는 TLS 버전과 cipher suite 목록을 보냅니다. 서버는 그 목록 중에서 자기도 지원하고, 자기가 가진 인증서와도 맞는 버전 하나와 cipher suite 하나를 골라야 합니다. 고를 게 없으면 alert를 보내고 연결을 끊습니다.

여기서 두 가지를 알 수 있습니다.

  • 인증서는 아직 검사하지도 않았습니다. 인증서 검증 전에 일어나는 오류라서, 인증서를 갱신하거나 교체해도 해결되지 않습니다. 그 이름의 인증서가 아예 없는 경우(아래 원인 3)만 예외입니다.
  • 크롬은 이 오류 이름을 넓게 씁니다. 서버가 일반적인 handshake_failure alert로 응답하면, 서버의 실제 이유가 다른 것이어도 크롬은 ERR_SSL_VERSION_OR_CIPHER_MISMATCH로 보여 줍니다. 그래서 ‘버전이나 암호화 방식’은 가장 유력한 설명일 뿐, 확정은 아닙니다.

파이어폭스는 같은 실패를 SSL_ERROR_UNSUPPORTED_VERSION(공통 TLS 버전 없음)과 SSL_ERROR_NO_CYPHER_OVERLAP(공통 cipher 없음)으로 나눠서 보여 줍니다.

방문자라면

같은 주소를 휴대폰에서, 와이파이가 아닌 모바일 데이터로 열어 보세요.

  • 거기서도 안 된다면: 사이트 설정 문제입니다. 내 기기에서 할 수 있는 일은 없고, 공개 웹사이트를 위해 브라우저 보안 설정을 낮춰서도 안 됩니다. 사이트 운영자에게 알리세요.
  • 거기서는 된다면: 내 네트워크나 컴퓨터의 무언가가 끼어 있는 것입니다. 회사 방화벽의 TLS 검사나 백신의 ‘HTTPS 검사’가 브라우저 대신 핸드셰이크를 하다가 실패하는 경우가 있습니다(원인 4).

정당한 예외가 하나 있습니다. 내 네트워크 안에 있는 오래된 공유기, NAS, 프린터, 서버 관리 카드의 관리 페이지입니다. 이런 기기는 TLS 1.0만 쓰는 경우가 많습니다. 진짜 해결책은 펌웨어 업데이트입니다. 그 전까지는 파이어폭스 about:config의 security.tls.version.min(1 = TLS 1.0, 기본값 3 = TLS 1.2)을 낮추면 들어갈 수 있습니다. 작업이 끝나면 되돌려 두세요.

내 사이트라면: 서버가 실제로 무엇을 제공하는지 확인

설정 파일을 읽고 짐작하지 마세요. 실패하는 바로 그 호스트명으로 서버에 직접 물어봐야 합니다. 이름마다 답이 다를 수 있기 때문입니다.

먼저 최신 브라우저가 쓰는 두 버전부터 확인합니다.

openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/null
openssl s_client -connect example.com:443 -servername example.com -tls1_2 </dev/null

핸드셰이크가 되면 인증서 체인과 함께 New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 같은 줄이 나옵니다. 실패하면 끝부분에 alert protocol version(alert 번호 70: 서버가 이 버전을 지원하지 않음)이나 alert handshake failure(alert 번호 40: 공통 cipher가 없거나 서버가 다른 이유로 거부)가 보이고, 대개 no peer certificate available이 뒤따릅니다.

둘 다 실패하면 서버가 옛 버전만 쓰는지 확인합니다.

openssl s_client -connect example.com:443 -servername example.com -tls1 -cipher 'DEFAULT:@SECLEVEL=0' </dev/null

@SECLEVEL=0이 필요한 이유는 OpenSSL 3.x가 기본 보안 수준에서 TLS 1.0·1.1을 아예 제안하지 않기 때문입니다. 배포판에 따라서는 빌드에서 빼 버리기도 합니다. no protocols available이 나오면 서버가 아니라 내 OpenSSL이 거부한 것입니다.

서버가 받아 주는 버전과 cipher 전체 목록은 이렇게 봅니다.

nmap --script ssl-enum-ciphers -p 443 example.com

서버가 받아 주는 프로토콜 버전마다 그 아래 cipher suite 목록을 보여 줍니다. testssl.sh가 설치돼 있다면 testssl.sh example.com이 더 자세한 보고서를 줍니다.

curl -v https://example.com/으로 빠르게 교차 확인할 수도 있습니다. 성공하면 SSL connection using으로 시작하는 줄에 버전과 cipher가 나옵니다.

SSL 점검은 우리 네트워크 밖에서 핸드셰이크를 하고, 협상된 버전과 그 이름으로 돌아온 인증서를 보여 줍니다. 점검은 되는데 내 컴퓨터에서만 안 된다면, 문제는 서버가 아니라 내 쪽 네트워크에 있습니다.

흔한 원인

1. 서버가 TLS 1.0이나 1.1만 지원함

크롬과 파이어폭스는 2020년(크롬 84, 파이어폭스 78)에 TLS 1.0·1.1을 기본으로 껐고, RFC 8996이 2021년에 두 버전을 공식적으로 폐기했습니다. 크롬은 처음에는 ‘계속 진행’을 누를 수 있는 경고를 보여 줬지만, 지금 버전은 그냥 거부하고 그 거부가 바로 이 오류입니다. 최신 버전이 TLS 1.1인 서버는 지금의 어떤 브라우저와도 공통 버전이 없습니다.

아직 이런 서버가 남아 있는 곳은 다음과 같습니다.

  • Windows Server 2008 R2와 그보다 오래된 IIS. TLS 1.2 자체는 있지만, 누군가 레지스트리에서 켜지 않으면 서버 쪽에서는 꺼져 있습니다.
  • OpenSSL 1.0.1보다 오래된 버전에 링크된 소프트웨어. TLS 1.2가 아예 없습니다.
  • 오래된 Java 애플리케이션 서버, 장비에 내장된 웹 관리 화면, 몇 년째 펌웨어를 올리지 않은 어플라이언스.
  • 오래된 블로그 글에서 복사해 온 ssl_protocols TLSv1; 같은 설정 한 줄.

해결: TLS 1.2와 1.3을 켭니다. 소프트웨어가 TLS 1.2를 못 한다면 업그레이드하거나, 앞에 TLS를 제대로 처리하는 리버스 프록시(nginx, HAProxy, CDN)를 둡니다. Windows에서는 HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols 아래에 프로토콜 스위치가 있고, IIS Crypto 같은 도구를 쓰면 직접 레지스트리를 고치지 않아도 됩니다.

2. cipher 목록에 최신 브라우저가 받아 주는 것이 없음

서버는 TLS 1.2를 지원하는데, cipher 목록에는 브라우저가 이미 없앤 것들만 있는 경우입니다.

  • RC4. 2015년 RFC 7465가 TLS에서 금지했고, 2016년 초 주요 브라우저에서 모두 빠졌습니다.
  • 3DES, DES, export 등급, NULL suite.
  • TLS_DHE_*(유한체 Diffie-Hellman) suite. 크롬은 2016년에 뺐습니다.
  • TLS_RSA_*만 있는 목록(ECDHE 없이 RSA로 키를 교환하는 방식). 크롬은 이것들을 기본에서 빼 나가고 있으므로, 이것만으로 된 목록은 언제 깨져도 이상하지 않습니다.

더 알아차리기 어려운 경우도 있습니다. cipher 목록에는 ECDSA용 suite(ECDHE-ECDSA-…)만 있는데 인증서는 RSA인 경우, 또는 그 반대입니다. TLS 1.2에서는 cipher suite가 쓸 수 있는 인증서 종류를 정합니다. 그래서 cipher 목록도 있고 인증서도 있는데, 그 인증서에 맞는 suite가 하나도 없는 상태가 됩니다.

cipher 문자열을 적용하기 전에 실제로 무엇으로 풀리는지 확인하세요.

openssl ciphers -v 'ECDHE+AESGCM:ECDHE+CHACHA20'

출력이 비어 있거나 Error in cipher list가 나오면 서버는 아무것도 제공하지 못합니다.

해결: 손으로 조합하지 말고 검증된 목록을 쓰세요(아래 설정 참고). Mozilla SSL Configuration Generator의 ‘intermediate’ 프로필은 거의 모든 공개 사이트에 무난한 기본값입니다.

3. 그 이름의 인증서가 없음 (특히 CDN)

가장 놓치기 쉬운 원인입니다. 겉보기엔 버전 문제인데 실제로는 인증서 문제이기 때문입니다. Cloudflare를 비롯한 일부 엣지는 브라우저가 요청한 호스트명(SNI 이름)의 인증서가 없으면 핸드셰이크를 끊어 버립니다. 크롬은 이것을 ERR_SSL_VERSION_OR_CIPHER_MISMATCH로 보여 줍니다.

Cloudflare에서 흔한 경우는 이렇습니다.

  • 두 단계 이상 깊은 이름을 프록시로 켬. 무료 Universal SSL 인증서는 example.com과 *.example.com만 커버합니다. 와일드카드는 이름 한 단계만 대신하므로 dev.shop.example.com에는 인증서가 없습니다. *.shop.example.com을 포함하는 Advanced Certificate(또는 Total TLS)가 필요하거나, 그 레코드를 DNS 전용으로 바꿔 내 서버가 자기 인증서를 내게 해야 합니다.
  • 방금 추가한 도메인이나 호스트명. 엣지 인증서가 아직 발급 중입니다. 보통 몇 분이면 되지만 더 걸리기도 합니다. SSL/TLS → Edge Certificates에서 인증서가 대기(pending) 상태라면 기다리거나, 검증을 막고 있는 것을 고쳐야 합니다.
  • Universal SSL을 꺼 두었고 그 이름을 커버하는 다른 인증서도 없음.

같은 IP에 openssl로 확인할 수 있습니다. -servername example.com은 되는데 -servername dev.shop.example.com은 handshake alert로 실패한다면, 서버는 정상이고 그 깊은 이름에 인증서가 없을 뿐입니다.

4. 중간에 있는 무언가가 핸드셰이크를 깨뜨림

밖에서는 서버가 정상인데 특정 사무실이나 특정 컴퓨터에서만 실패한다면 그 사이를 보세요. 회사의 TLS 검사 방화벽과 백신의 HTTPS 검사는 브라우저의 TLS 연결을 자기가 받아 끝내고, 사이트로는 자기 연결을 따로 엽니다. 이 장비의 TLS 구현이 오래됐으면 최신 서버와 협상에 실패하거나, 브라우저에 받아 줄 수 없는 것을 제시합니다. 그러면 멀쩡한 사이트에서 이 오류가 납니다.

해결: 내가 관리하는 다른 네트워크에서 같은 주소를 열어 비교하세요. 회사 기기라면 검사 장비를 업데이트하거나 그 사이트를 예외로 둬야 하므로, 방화벽 담당자에게 요청할 일입니다.

5. 오래된 쪽이 클라이언트임

오류가 최신 크롬이 아니라 앱, 스크립트, 장비에서 난다면 질문을 뒤집어 보세요. 서버는 최신인데 클라이언트가 오래된 것일 수 있습니다.

  • 안드로이드 5.0 미만은 TLS 1.2가 기본으로 켜져 있지 않고, 안드로이드 10 미만에는 TLS 1.3이 없습니다. TLS 1.3 전용 서버는 아직 쓰이는 기기 상당수를 막아 버립니다.
  • 오래된 .NET·PowerShell 코드가 쓰는 Windows 내장 TLS 스택은 Windows 11과 Windows Server 2022부터 TLS 1.3이 켜져 있습니다.
  • CDN 설정을 지나치게 강화해도 같습니다. Cloudflare의 최소 TLS 버전(Minimum TLS Version)을 1.3으로 두거나 로드밸런서 정책을 TLS 1.3 전용으로 두면 1.2까지만 되는 클라이언트는 모두 거부됩니다.

공개 사이트라면 TLS 1.2와 1.3을 함께 여는 것이 맞는 기준입니다. TLS 1.3 전용은 클라이언트를 내가 통제하는 API에서나 괜찮습니다.

동작하는 설정

nginx라면 Mozilla intermediate 프로필을 조금 줄인 다음 설정이면 됩니다.

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;

ssl_ciphers는 TLS 1.2 이하에만 적용됩니다. TLS 1.3은 따로 정해진 몇 개의 suite만 있고 모두 안전하며, OpenSSL 1.1.1 이상이면 자동으로 켜집니다.

Apache라면:

SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
SSLHonorCipherOrder off

바꿨는데 효과가 없어 보일 때 걸리는 함정이 두 가지 있습니다.

  • 설정이 여러 곳에 있음. 예를 들어 certbot의 nginx 플러그인은 자체 ssl_protocols·ssl_ciphers가 들어 있는 /etc/letsencrypt/options-ssl-nginx.conf를 include합니다. nginx -T | grep -n 'ssl_protocols\|ssl_ciphers'로 nginx가 실제로 읽어 들인 값을 모두 확인하세요. Apache는 apachectl -S로 443에서 어느 가상 호스트가 응답하는지 볼 수 있습니다.
  • TLS를 끝내는 곳이 생각과 다름. 앞에 CDN이나 로드밸런서가 있으면 브라우저는 내 서버가 아니라 그 장비와 협상합니다. 최소 버전이나 보안 정책은 그쪽에서 바꿔야 합니다.

저장만 하지 말고 리로드하세요. nginx -t && systemctl reload nginx, 또는 apachectl configtest && systemctl reload apache2입니다.

고친 다음에

  • 루트 도메인만이 아니라 사람들이 쓰는 모든 호스트명에 openssl s_client -tls1_2·-tls1_3 테스트를 다시 돌리세요.
  • SSL 점검으로 바깥 클라이언트가 이제 TLS 1.2나 1.3으로 협상하고 올바른 인증서를 받는지 확인하세요.
  • 443 포트 자체가 밖에서 응답하지 않는다면 다른 문제입니다. 포트 점검으로 연결이 되는지부터 보세요.

비슷해 보이는 다른 오류

  • ERR_SSL_PROTOCOL_ERROR: 핸드셰이크가 덜 구체적인 방식으로 깨집니다. 서버가 443 포트에서 평문 HTTP로 응답하는 경우가 대표적입니다.
  • NET::ERR_CERT_COMMON_NAME_INVALID: 핸드셰이크는 성공했지만 인증서에 입력한 이름이 없습니다.
  • NET::ERR_CERT_DATE_INVALID: 핸드셰이크는 성공했지만 인증서가 만료됐거나 아직 유효 기간 전이거나, 기기 시계가 틀렸습니다.
  • Cloudflare 525 에러: 같은 종류의 핸드셰이크 실패인데, 브라우저와 Cloudflare 사이가 아니라 Cloudflare와 원 서버 사이에서 일어납니다.

내 도메인에서 바로 확인

무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.