525 에러가 뜻하는 것

Cloudflare를 앞에 두면 HTTPS 요청은 두 구간으로 나뉩니다. 방문자의 브라우저에서 Cloudflare까지, 그리고 Cloudflare에서 내 서버(원 서버, origin)까지입니다. 구간마다 TLS 핸드셰이크와 인증서가 따로 있습니다.

525 에러는 두 번째 구간의 문제입니다. 방문자는 Cloudflare까지 잘 왔고, Cloudflare는 원 서버와 TCP 연결을 맺었는데, 그 위에서 TLS 핸드셰이크가 실패했습니다. Cloudflare 오류 화면에는 이것이 세 칸으로 나옵니다. 브라우저 정상, Cloudflare 정상, 호스트 오류.

525는 Cloudflare가 원 서버와 HTTPS로 통신할 때만 납니다. 즉 SSL/TLS 모드가 Full이나 Full (strict)일 때입니다. Flexible 모드에서는 Cloudflare가 원 서버와 평문 HTTP로 통신하므로 핸드셰이크를 시도하지 않습니다.

방문자라면

내 쪽에서 고칠 것은 없습니다. 끊어진 곳은 Cloudflare와 사이트 서버 사이입니다. 캐시를 지우거나, 브라우저를 바꾸거나, 와이파이를 바꿔도 소용없습니다. 잠시 후 다시 시도하거나 사이트 운영자에게 알리세요. 오류가 난 시각과 오류 화면 아래쪽의 Ray ID를 함께 전하면 실패한 요청을 특정할 수 있습니다.

어떤 Cloudflare 에러인가

Cloudflare의 52x 코드는 원 서버와의 연결이 어디까지 갔는지를 알려 줍니다. 이웃 코드를 알아 두면 시간이 절약됩니다. 코드마다 가리키는 층이 다르기 때문입니다.

코드멈춘 곳
521원 서버가 TCP 연결을 거부함(듣고 있는 프로그램이 없거나 방화벽이 거부)
522TCP 연결 시간 초과(패킷이 버려지거나, 원 서버에 닿지 않거나, 과부하)
525TCP 연결은 됐지만 TLS 핸드셰이크가 실패함
526핸드셰이크는 끝났지만 Full (strict)에서 원 서버 인증서가 유효하지 않음
520연결은 됐지만 원 서버의 응답이 비어 있거나 Cloudflare가 해석할 수 없음

그래서 525는 쓸모 있는 정보를 줍니다. 네트워크 경로는 정상이고 포트도 열려 있다는 것입니다. 문제는 TLS가 시작된 뒤 원 서버가 무엇을 하느냐에 있습니다.

많은 사람이 놀라는 점이 하나 있습니다. Full 모드에서 Cloudflare는 원 서버 인증서를 전혀 검증하지 않습니다. 자체 서명이든, 만료됐든, 다른 이름용이든 다 받아 줍니다. 그래서 원 서버 인증서 만료는 525의 원인이 되지 않습니다. 만료는 526을 내고, 그것도 Full (strict)에서만입니다. 모드별 차이는 Cloudflare SSL 모드에 정리돼 있습니다.

Cloudflare를 거치지 않고 원 서버를 직접 점검하기

원 서버의 실제 IP가 필요합니다. Cloudflare DNS 대시보드의 A 또는 AAAA 레코드 값입니다. 프록시를 켠 레코드는 공개 DNS에서 Cloudflare 주소만 나오므로 거기서는 찾을 수 없습니다.

그다음 Cloudflare가 하는 일을 그대로 해 봅니다. 그 IP에 연결하면서 SNI로 내 호스트명을 요청하는 것입니다.

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

--resolve를 쓰면 curl이 그 IP로 연결하면서도 올바른 호스트명을 보내므로, Cloudflare가 보내는 요청과 같은 조건이 됩니다. Full 모드처럼 인증서 유효성을 무시하려면 -k를 붙이세요. -k 없이 Origin CA 인증서를 쓰는 서버를 테스트하면 내 컴퓨터에서는 검증에 실패합니다. Cloudflare만 그 인증서를 신뢰하므로 정상입니다.

결과는 이렇게 읽습니다.

  • 인증서 체인과 New, TLSv1.3, Cipher is … 같은 줄이 나옴: 원 서버는 그 이름으로 핸드셰이크를 잘 합니다. 그래도 Cloudflare가 525를 낸다면 아래 원인 4와 5를 보세요.
  • wrong version number나 packet length too long: 포트는 응답하지만 TLS로 응답하지 않습니다. 대개 443 포트에서 평문 HTTP가 돌고 있습니다(원인 1).
  • alert handshake failure, 또는 no peer certificate available과 함께 연결이 끊김: 원 서버가 핸드셰이크를 거부했습니다(원인 2, 3).
  • 연결 거부나 시간 초과: 내 위치에서 보면 521·522 상황입니다. 원 서버 방화벽이 Cloudflare IP 대역만 허용하고 있다면 정상적인 결과입니다. 이때는 서버 안에서 127.0.0.1을 대상으로 같은 테스트를 하세요.

원 서버에 프록시를 거치지 않는(DNS 전용) 공개 호스트명이 있다면 SSL 점검으로 바깥에서 본 인증서와 협상된 TLS 버전을 확인할 수 있습니다. 포트 점검은 원 서버 IP의 443 포트에 아예 연결이 되는지 보여 주므로, ‘방화벽 문제’와 ‘TLS 문제’를 가를 수 있습니다.

흔한 원인

1. 443 포트에서 TLS로 말하는 프로그램이 없음

포트는 열려 있는데, 거기서 듣고 있는 서버가 HTTPS를 하지 않는 경우입니다. 이렇게 되는 흔한 경로는 다음과 같습니다.

  • nginx에서 listen 443 ssl;이 아니라 listen 443;으로 적음. 443 포트에서 평문 HTTP를 아무렇지 않게 서비스합니다.
  • Apache의 <VirtualHost *:443> 블록에 SSLEngine on이 없거나 mod_ssl이 로드되지 않음.
  • 원 서버에 HTTPS 사이트가 아예 없음. 사이트를 Cloudflare 뒤로 옮긴 직후에 흔합니다. 원래 HTTP만 서비스하던 서버인데 누군가 모드를 Flexible에서 Full로 바꾼 경우입니다.
  • 국내 호스팅을 포함한 공유 호스팅에서 SSL이 별도(대개 유료) 부가 서비스인데, 이 도메인에는 신청하지 않음.

sudo ss -ltnp | grep ':443'으로 무엇이 듣고 있는지 보고, curl -v http://203.0.113.10:443/도 해 보세요. 443 포트에서 평문 HTTP 응답이 온다면 원인을 찾은 것입니다.

해결: 인증서를 넣은 HTTPS server 블록을 제대로 만드세요. 인증서가 없다면 Cloudflare Origin CA 인증서를 쓰면 됩니다(아래 참고).

2. 원 서버에 이 호스트명의 인증서가 없음

Cloudflare는 방문자가 요청한 호스트명을 SNI로 보냅니다. 원 서버는 그 이름으로 설정을 고르고, 맞는 server 블록이 없으면 443 포트의 기본 설정으로 넘어갑니다.

Full 모드에서는 기본 설정이 어떤 인증서를 돌려주든 상관없습니다. Cloudflare가 무엇이든 받아 주기 때문입니다. 핸드셰이크가 실패하는 것은 기본 설정이 인증서를 아예 내놓지 못할 때뿐입니다.

  • 443 포트의 nginx 기본 server에 ssl_certificate가 없음. nginx 로그에 no "ssl_certificate" is defined in server listening on SSL port while SSL handshaking 같은 메시지가 남습니다.
  • 모르는 이름을 거부하도록 설정된 기본 server(흔히 쓰는 보안 강화 설정인 nginx ssl_reject_handshake on;)가 있는데, 새 호스트명을 server_name에 추가하지 않음.
  • 호스팅 관리 화면에 도메인은 추가했지만 서브도메인은 추가하지 않음.

구별하는 방법: 같은 원 서버 IP에 대해 openssl s_client -servername이 한 호스트명으로는 되고 다른 호스트명으로는 실패합니다.

해결: 그 호스트명을 올바른 server 블록(server_name, ServerAlias, 호스팅 관리 화면의 도메인 목록)에 추가하고, 그 이름을 커버하는 인증서를 넣으세요.

3. 공통 프로토콜 버전이나 cipher가 없음

원 서버가 Cloudflare가 원 서버 연결에 쓰지 않는 것, 예를 들어 아주 오래된 TLS 버전이나 폐기된 cipher suite만 제공하는 경우입니다. 오래된 장비, 구형 Windows 서버, 아주 오래된 OpenSSL로 빌드한 소프트웨어가 주로 그렇습니다. Cloudflare는 원 서버 연결에 쓰는 cipher suite 목록을 문서로 공개하고 있으니, 원 서버가 받아 주는 목록과 비교해 보세요.

nmap --script ssl-enum-ciphers -p 443 203.0.113.10

브라우저 쪽 ERR_SSL_VERSION_OR_CIPHER_MISMATCH와 같은 논리입니다. ECDHE와 AES-GCM 또는 ChaCha20 suite로 TLS 1.2·1.3을 켜면 해결됩니다.

4. 방화벽이나 보안 도구가 핸드셰이크를 끊음

서버 안에서 테스트하면 되는데 Cloudflare는 계속 525를 받습니다. 무언가가 Cloudflare의 연결을 내 연결과 다르게 다루고 있는 것입니다.

  • fail2ban, CrowdSec, 호스팅 방화벽이 Cloudflare IP를 차단함. Cloudflare 뒤에서는 모든 방문자 트래픽이 Cloudflare 주소에서 들어오므로, ‘요청을 너무 많이 보내는 IP’를 막는 규칙이 결국 Cloudflare 엣지를 막아 버립니다. 그 엣지를 거치는 방문자만 오류를 보고 나머지는 멀쩡합니다. 어떻게 보이는지는 차단 방식에 따라 다릅니다. 패킷을 버리면 522, 거부하면 521, 핸드셰이크 도중 연결을 리셋하면 525입니다.
  • 원 서버 쪽 IPS나 WAF가 TLS를 검사하다가 마음에 들지 않는 연결을 리셋함.

해결: Cloudflare가 공개한 IP 대역(https://www.cloudflare.com/ips-v4, https://www.cloudflare.com/ips-v6)을 허용하고 자동 차단 대상에서 빼세요. 웹 서버가 CF-Connecting-IP 헤더로 실제 방문자 IP를 기록하게 설정하면, 차단이 Cloudflare가 아니라 방문자를 향하게 됩니다.

5. 원 서버 중 일부만 문제

525가 났다 안 났다 한다면 그 호스트명에 원 서버가 여러 대인지 보세요. 프록시를 켠 A 레코드가 여러 개이거나, Cloudflare 로드밸런서 풀이 있거나, 내 로드밸런서 뒤에 서버가 여러 대인 경우입니다. 그중 한 대만 인증서가 없거나 TLS 설정이 다르면, 요청 일부에서만 525가 납니다.

위의 openssl 명령으로 원 서버 IP를 하나씩 테스트하세요. 부하가 몰릴 때만 나는 간헐적 오류는 원 서버가 워커나 연결 수를 다 써서 핸드셰이크를 끝내지 못하는 경우일 수도 있습니다. 원 서버의 오류 로그에 흔적이 남습니다.

6. 원 서버가 클라이언트 인증서를 요구함

원 서버가 클라이언트 인증서(상호 TLS)를 요구하는데 Cloudflare가 보내지 않으면 연결이 실패합니다. 서버에 따라 핸드셰이크 실패(525)가 되기도 하고, 핸드셰이크는 끝난 뒤 오류 페이지를 돌려주기도 합니다. nginx에서 ssl_verify_client on을 쓰면 후자이고, 400 “No required SSL certificate was sent”가 나옵니다.

해결: 이 방식으로 원 서버를 Cloudflare 전용으로 묶을 생각이었다면 Cloudflare에서 Authenticated Origin Pulls를 켜서 클라이언트 인증서를 보내게 하세요. 의도한 게 아니라면 클라이언트 인증서 요구를 없애세요.

Flexible로 바꾸면 어떨까

오류는 가려지지만 문제가 두 개 생깁니다. Cloudflare와 내 서버 사이가 평문 HTTP가 되어, 그 경로에 있는 누구든 트래픽을 읽고 바꿀 수 있는데도 방문자에게는 자물쇠가 보입니다. 그리고 원 서버가 HTTP를 HTTPS로 리다이렉트한다면(대부분 그렇습니다) Cloudflare의 HTTP 요청이 HTTPS로 리다이렉트되고, Cloudflare는 그 주소를 다시 HTTP로 가져오고, 방문자는 ERR_TOO_MANY_REDIRECTS를 보게 됩니다.

장애 중 5분짜리 임시방편이라면 괜찮습니다. 해결책으로는 안 됩니다.

호스트명 하나만 존 전체와 다른 모드로 돌고 있지 않은지도 확인하세요. Configuration Rule이나 예전 Page Rule로 호스트명별 SSL 모드를 정할 수 있는데, 존 설정이 맞아 보이면 잊기 쉽습니다.

깔끔한 해결: Origin CA와 Full (strict)

대부분의 사이트에서 가장 간단하고 튼튼한 구성은 이렇습니다.

  1. Cloudflare 대시보드에서 SSL/TLS → Origin Server → Create Certificate. 루트와 와일드카드(example.com, *.example.com)를 넣습니다. 유효 기간은 최대 15년까지 길게 고를 수 있습니다.
  2. 인증서와 개인 키를 원 서버에 설치합니다. 프록시를 거치는 모든 호스트명의 443 server 블록에 넣습니다.
  3. 웹 서버를 리로드합니다. nginx -t && systemctl reload nginx.
  4. 위의 openssl s_client -servername 명령으로 테스트한 뒤 모드를 Full (strict)로 바꿉니다.

브라우저는 Origin CA 인증서를 신뢰하지 않는다는 점을 기억하세요. 나중에 레코드를 DNS 전용으로 바꾸면 방문자는 그 이름에서 인증서 오류를 보게 됩니다. 그럴 때는 공인 인증서를 쓰세요(Let’s Encrypt도 Cloudflare 뒤에서 잘 동작합니다).

고친 다음에

  • 페이지를 새로고침하세요. 오류 화면마다 새 Ray ID가 붙으므로, 새 Ray ID로 여전히 525가 나온다면 원 서버가 아직 실패하고 있는 것입니다.
  • 신고된 호스트명 하나만이 아니라 프록시를 거치는 모든 호스트명을 테스트하세요.
  • 공개 호스트명에 SSL 점검을 돌려 방문자 쪽 엣지 인증서도 정상인지 확인하세요.

비슷해 보이는 다른 오류

내 도메인에서 바로 확인

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