이 오류가 뜻하는 것

크롬은 보안 연결을 제공할 수 없고 사이트가 잘못된 응답을 보냈다는 화면과 함께, 그 아래에 ERR_SSL_PROTOCOL_ERROR를 표시합니다.

HTTPS 연결은 TLS 핸드셰이크로 시작합니다. 크롬이 ClientHello를 보내면 ServerHello가 돌아오고, 이어서 인증서와 키 교환이 정해진 형식대로 오가야 합니다. ERR_SSL_PROTOCOL_ERROR는 돌아온 것이 그렇지 않았다는 뜻입니다. 받은 바이트가 TLS로 해석되지 않았거나, 메시지가 순서에 맞지 않게 왔거나, 크롬이 프로토콜 위반으로 분류하는 방식으로 연결이 깨졌습니다.

이것만으로도 범위가 꽤 좁혀집니다. 연결 자체는 됐습니다. DNS가 해석됐고 443 포트로 TCP 연결이 열렸습니다. 그렇지 않았다면 ERR_NAME_NOT_RESOLVED나 ERR_CONNECTION_REFUSED가 났을 겁니다. 그리고 핸드셰이크는 인증서를 검사하는 단계까지 가지 못했습니다. 갔다면 NET::ERR_CERT_* 계열 오류가 났을 겁니다.

크롬이 보여 주는 것멈춘 곳
ERR_CONNECTION_REFUSED / ERR_CONNECTION_TIMED_OUT443 포트로 TCP 연결이 안 됨
ERR_SSL_PROTOCOL_ERROR연결은 됐지만 응답이 올바른 TLS가 아니거나 핸드셰이크가 규칙을 어김
ERR_SSL_VERSION_OR_CIPHER_MISMATCH양쪽 모두 TLS를 말했지만 버전이나 암호 방식이 맞지 않음(또는 CDN에 그 이름의 인증서가 없음)
NET::ERR_CERT_*핸드셰이크가 인증서까지 갔고, 인증서가 검사를 통과하지 못함

가장 흔한 경우인 ‘HTTPS 포트에서 평문 HTTP’를 파이어폭스는 SSL_ERROR_RX_RECORD_TOO_LONG으로 표시합니다.

내 쪽 문제인가, 사이트 문제인가

이것부터 정하세요. 나머지가 전부 여기에 달려 있습니다.

  • 와이파이를 끄고 휴대폰 모바일 데이터로 같은 주소를 열어 봅니다.
  • SSL 점검을 돌려 봅니다. 서울에 있는 서버에서 실제 핸드셰이크를 하고, 협상된 TLS 버전과 인증서를 보여 주거나 핸드셰이크가 실패했다고 알려 줍니다.

어디서나 실패한다면 사이트가 고장 난 것이니 운영자 부분으로 넘어가세요. 다른 곳에서는 되고 내 기기나 내 네트워크에서만 실패한다면 다음 부분을 보세요.

방문자라면

자주 관련되는 순서대로 보면 이렇습니다.

  • HTTPS 검사 기능이 있는 백신이나 방화벽. 모든 TLS 연결 한가운데에 끼어 있다가 가끔 하나를 망가뜨립니다. HTTPS/SSL 검사 옵션을 잠시 끄고 다시 시도해 보세요.
  • VPN이나 프록시. 연결을 끊고 다시 시도합니다. 시스템 프록시가 설정돼 있는지도 확인하세요(윈도우: 설정 → 네트워크 및 인터넷 → 프록시).
  • 필터링하거나 로그인을 요구하는 네트워크. 일부 호텔·학교·회사 네트워크는 443 포트에 직접 응답하면서 로그인 화면이나 차단 화면을 평문 HTTP로 보냅니다. 브라우저 입장에서는 그것이 정확히 프로토콜 오류입니다. http:// 페이지를 열어 로그인 화면을 띄우거나 다른 네트워크로 시도해 보세요.
  • 틀린 시스템 시계. 보통은 이 오류가 아니라 인증서 날짜 오류를 내지만, 확인하는 데 1초면 됩니다.

인터넷에 도는 조언(SSL 상태 지우기, DNS 캐시 비우기, QUIC 끄기, 크롬 초기화)은 대부분 여러분이 겪고 있지 않을 문제를 겨냥합니다. 모두에게 실패하는 사이트라면 하나도 도움이 되지 않습니다. 나에게만 실패한다면 원인은 대개 위 목록 안에 있습니다.

사이트 운영자라면: 443 포트가 실제로 무엇을 말하는지 보기

명령 두 개면 거의 다 알 수 있습니다.

curl -v https://www.example.com/ -o /dev/null
openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null

출력별로 뜻하는 것:

wrong version number(또는 packet length too long):

curl: (35) OpenSSL/3.0.13: error:0A00010B:SSL routines::wrong version number

서버가 응답은 했지만 TLS가 아니었습니다. 443 포트에서 무언가가 평문 HTTP를 말하고 있습니다. 이렇게 확인합니다.

curl -v http://www.example.com:443/

평범한 HTTP 응답이 돌아오면 원인을 찾은 것입니다(아래 원인 1). 반대로 nginx가 400 The plain HTTP request was sent to HTTPS port라고 답한다면 443 포트는 TLS를 제대로 말하고 있고 문제는 다른 곳에 있습니다.

**unexpected eof while reading**이나 **no peer certificate available**만 나오는 경우: 서버가 연결을 받고는 핸드셰이크를 끝내지 않고 끊었습니다. 이 이름으로 쓸 수 있는 인증서가 없는 TLS 리스너(원인 3)나 중간 장비의 연결 리셋(원인 5)에서 흔히 보입니다.

**alert handshake failure**나 alert protocol version: 서버는 TLS를 말하고 있고 내 제안을 거절한 것입니다. 실제로는 버전·암호 방식 문제이니 ERR_SSL_VERSION_OR_CIPHER_MISMATCH를 보세요.

**Connection refused**나 시간 초과: 아직 TLS 문제가 아닙니다. 443에 닿는 것이 없습니다. 포트 점검으로 확인하고, ERR_CONNECTION_REFUSED처럼 방화벽과 리스너를 보세요.

서버 쪽 흔한 원인

1. 443 포트에서 평문 HTTP가 나감

압도적으로 흔한 원인입니다. 이렇게 생깁니다.

  • nginx에서 listen 443 ssl; 대신 listen 443;을 썼습니다. nginx는 아무 불평 없이 443에서 평문 HTTP를 서비스합니다. include된 파일까지 포함해 443 리스너를 전부 찾습니다.

    sudo nginx -T 2>/dev/null | grep -nE 'listen\s+[^;]*443'

    모든 줄에 ssl이 있어야 합니다.

  • Apache에서 <VirtualHost *:443>에 SSLEngine on이 없거나, mod_ssl이 로드되지 않았습니다(apachectl -M | grep ssl).

  • Docker가 컨테이너 포트를 잘못 연결했습니다. -p 443:80은 HTTPS 트래픽을 HTTP를 말하는 컨테이너 포트로 보냅니다. docker ps에 0.0.0.0:443->80/tcp로 보입니다.

  • TCP 로드밸런서(예: 443에 TCP 리스너를 둔 AWS Network Load Balancer)가 백엔드의 HTTP 포트로 넘깁니다. TCP 리스너는 바이트를 그대로 통과시킵니다. 로드밸런서에서 TLS를 종료하거나(인증서를 단 TLS 리스너), TLS를 말하는 백엔드 포트로 넘겨야 합니다.

  • 가정·사무실 공유기가 외부 443을 내부 80 포트로 포워딩합니다.

  • 애플리케이션 서버(Node.js, Python, Go)가 TLS 프록시 없이 http.createServer나 일반 listen으로 443에 떠 있습니다.

해결: 443에서 듣는 것이 무엇이든 인증서를 달고 TLS를 말하게 합니다. nginx라면:

server {
    listen 443 ssl;
    http2 on;            # nginx 1.25.1 이상. 이전 버전은 "listen 443 ssl http2;"
    server_name www.example.com;
    ssl_certificate     /etc/letsencrypt/live/www.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
}

그다음 sudo nginx -t && sudo systemctl reload nginx.

2. HTTP만 말하는 포트로 HTTPS를 보냄

거울상입니다. 서버는 맞고 URL이 틀렸습니다. 8080에서 HTTP만 하는 앱 서버에 https://example.com:8080/으로 접속하면 같은 오류가 납니다. 개발용·관리용 포트에 https://를 붙이는 링크, 리다이렉트, 설정을 찾아보세요.

로컬 개발의 함정이 하나 있습니다. .dev와 .app 최상위 도메인은 크롬의 HSTS preload 목록에 들어 있어서 크롬이 항상 HTTPS로 접속합니다. 로컬에서 myproject.dev를 쓰면서 평문 HTTP로 서비스하면, 크롬이 요청을 HTTPS로 올리고 HTTP 응답을 받게 됩니다. localhost나 .test 이름을 쓰거나, mkcert로 로컬 TLS를 설정하세요.

3. TLS는 설정됐지만 이 호스트명용이 아님

서버가 443에서 TLS를 말하고 SNI 이름으로 설정을 고릅니다. 맞는 server 블록이 없으면 nginx는 그 포트의 기본 서버로 물러서고, 기본 서버에 인증서가 없으면 핸드셰이크를 끝낼 수 없습니다. nginx 오류 로그에 이렇게 남습니다.

no "ssl_certificate" is defined in server listening on SSL port while SSL handshaking

서버가 정확히 어떻게 포기하느냐에 따라 크롬은 이것을 ERR_SSL_PROTOCOL_ERROR나 ERR_CONNECTION_CLOSED로 표시합니다. 같은 IP에 대해 openssl s_client -servername이 어떤 이름에서는 되고 다른 이름에서는 실패한다면 이 경우입니다.

재현한 직후 로그를 봅니다.

sudo tail -n 50 /var/log/nginx/error.log
sudo journalctl -u nginx --since "10 min ago"

해결: ssl_certificate가 있는 블록의 server_name에 호스트명을 추가하고, 기본 서버에도 인증서를 주세요. nginx 1.19.4 이상이라면 ssl_reject_handshake on;으로 모르는 이름을 핸드셰이크를 반쯤 하다 마는 대신 깔끔하게 거절할 수 있습니다.

관리형 호스팅에서도 도메인을 추가한 직후, 호스팅이 인증서를 발급하기 전까지 잠깐 같은 일이 생깁니다. 다른 것을 파기 전에 호스팅 관리 화면의 SSL 상태부터 확인하세요.

4. TLS 계층이 깨졌거나 잘못 설정됨

흔하지는 않지만 나옵니다.

  • 서버가 읽을 수는 있지만 쓸 수 없는 인증서·키 파일: 비어 있거나, 잘렸거나, 형식이 틀렸거나(PEM 자리에 DER), 인증서와 짝이 맞지 않는 키. 대부분의 서버는 이럴 때 시작을 거부하지만, 일부 앱 서버는 연결이 들어올 때에야 실패합니다. 파일이 제대로 읽히는지 확인하세요. openssl x509 -in fullchain.pem -noout -subject, openssl pkey -in privkey.pem -noout.
  • TLS를 종료하는 프록시 앞에 또 프록시가 있고, 한 구간은 TLS를 기대하는데 다른 구간은 HTTP를 보내는 경우. 방문자에서 애플리케이션까지 경로를 그리고 구간마다 HTTP인지 HTTPS인지 표시해 보세요. 어긋난 곳이 대개 바로 보입니다.
  • 최신 ClientHello에 제대로 답하지 못하는 오래되거나 임베디드된 TLS 구현. 소프트웨어나 TLS 라이브러리를 업데이트하는 것이 해결책이고, 방문자가 클라이언트 쪽에서 바꿀 만한 설정은 없습니다.

5. 서버 앞의 중간 장비

인터넷과 서버 사이의 IPS, 웹 방화벽, DDoS 필터, ‘SSL 검사’ 장비가 마음에 들지 않는 핸드셰이크를 리셋하거나 변형할 수 있습니다. 증상은 네트워크 안쪽에서(또는 서버에서 127.0.0.1로) 핸드셰이크하면 되는데 바깥에서는 실패하는 것입니다.

둘을 비교해 보세요.

# 서버 자신에서
openssl s_client -connect 127.0.0.1:443 -servername www.example.com </dev/null

그리고 바깥에서 SSL 점검을 돌립니다. 안쪽은 되고 바깥은 안 된다면 그 사이의 장비들과, 실패한 시각의 그 장비 로그를 보세요.

이 오류가 아닌 것: ERR_SSL_VERSION_OR_CIPHER_MISMATCH

둘을 자주 혼동합니다. ERR_SSL_VERSION_OR_CIPHER_MISMATCH에서는 서버가 올바른 TLS를 말하고 정식 경고(alert)로 답합니다. 브라우저와 공통되는 프로토콜 버전이나 암호 스위트가 없거나, CDN에서 아주 흔하게는 요청한 이름의 인증서가 아예 없는 경우입니다. TLS 1.0이나 1.1만 제공하는 오래된 서버도 대개 프로토콜 오류가 아니라 이쪽으로 나타납니다. 해결책도 다릅니다. TLS 1.2·1.3과 최신 암호 방식을 켜거나, CDN이 그 이름의 인증서를 발급하게 해야 합니다. ERR_SSL_VERSION_OR_CIPHER_MISMATCH를 보세요.

Cloudflare 뒤에 있다면 방문자는 내 서버가 아니라 Cloudflare와 TLS로 통신합니다. 그래서 원 서버가 고장 나면 ERR_SSL_PROTOCOL_ERROR가 아니라 Cloudflare의 525 에러가 납니다.

고친 다음에

  • curl -v https://www.example.com/이 핸드셰이크를 마치고 HTTP 상태 코드를 출력해야 합니다.
  • curl -v http://www.example.com:443/이 더는 평범한 페이지를 돌려주지 않아야 합니다.
  • SSL 점검에 인증서와 협상된 TLS 버전(1.2 또는 1.3)이 나와야 합니다.
  • HTTP 점검에서 http://가 루프 없이 한 번에 https://로 리다이렉트되는지 확인합니다.
  • 보고된 이름 하나만이 아니라 그 서버를 가리키는 모든 호스트명을 확인하세요.

관련 오류

내 도메인에서 바로 확인

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