521 에러가 뜻하는 것

Cloudflare를 앞에 둔 사이트에서는 모든 요청이 두 구간을 지납니다. 방문자의 브라우저에서 Cloudflare까지, 그리고 Cloudflare에서 내 서버(원 서버, origin)까지입니다. 521 에러 “Web server is down”은 두 번째 구간의 문제입니다. Cloudflare가 원 서버와 TCP 연결을 열려고 했는데 원 서버가 거부했습니다.

여기서 ‘거부’는 뜻이 분명합니다. Cloudflare의 연결 시도가 곧바로 거절당했다는 것이고, 대개 TCP reset입니다. 그 포트에서 듣는 프로그램이 없거나 방화벽 규칙이 연결을 거부하기 때문입니다. 패킷이 사라져서 Cloudflare가 결국 포기하는 ‘무응답’과는 다릅니다. 무응답은 522입니다.

오류 화면에는 세 칸이 나옵니다. 브라우저 정상, Cloudflare 정상, 호스트 오류. 이름과 달리 서버가 꼭 꺼진 것은 아닙니다. 그 포트에서 Cloudflare를 거부하고 있을 뿐입니다.

방문자라면

내 쪽에서 고칠 것은 없습니다. 새로고침, 캐시 삭제, 브라우저나 네트워크 변경 모두 소용없습니다. 실패가 Cloudflare와 사이트 서버 사이에서 일어나기 때문입니다. 잠시 후 다시 시도하세요. 운영자를 안다면 오류가 난 시각과 오류 화면 아래쪽의 Ray ID를 전해 주세요.

어떤 Cloudflare 에러인가

52x 코드는 Cloudflare와 원 서버의 연결이 어디까지 갔는지를 알려 줍니다.

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

그래서 521은 원 서버 주소까지의 네트워크 경로는 살아 있다는 것을 알려 줍니다. 그 주소의 기계가 응답했고, 그 응답이 ‘안 된다’였습니다.

Cloudflare가 두드리는 포트

무엇을 테스트하든 그 전에 Cloudflare가 어느 포트로 연결하는지부터 정리하세요. 한 포트에서 거부당했다고 해서 다른 포트 상태를 알 수는 없습니다. SSL/TLS 모드와 방문자가 들어온 방식에 따라 다릅니다.

SSL/TLS 모드방문자가 HTTPS로 옴방문자가 HTTP로 옴
Flexible원 서버 80 포트원 서버 80 포트
Full / Full (strict)원 서버 443 포트원 서버 80 포트

이 표에서 전형적인 함정 두 개가 나옵니다. 443만 열어 둔 원 서버는 Flexible 모드에서 521을 냅니다. 그리고 Full 모드인데 80 포트가 닫혀 있으면 http://로 들어온 방문자에게만 521이 납니다. scheme을 눈치채기 전까지는 무작위로 나는 것처럼 보입니다. Cloudflare에서 “Always Use HTTPS”를 켜면 HTTP 방문자를 엣지에서 리다이렉트하므로 80 포트까지 오지 않습니다. 모드별 차이는 Cloudflare SSL 모드에 자세히 정리돼 있습니다.

(Cloudflare는 8080, 8443 같은 다른 포트도 일부 프록시하며, 이때 방문자가 쓴 포트와 같은 포트로 원 서버에 연결합니다. URL에 그런 포트를 쓴다면 그 포트를 테스트하세요.)

1단계: 서버에서 듣고 있는 프로그램이 있나

원 서버에 접속해서 웹 서버와 소켓 상태를 봅니다.

sudo systemctl status nginx        # 또는 apache2 / httpd / caddy / 내 앱
sudo ss -ltnp '( sport = :80 or sport = :443 )'
State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
LISTEN  0       511     0.0.0.0:80          0.0.0.0:*          users:(("nginx",pid=812,fd=6))
LISTEN  0       511     0.0.0.0:443         0.0.0.0:*          users:(("nginx",pid=812,fd=7))
LISTEN  0       511     [::]:443            [::]:*             users:(("nginx",pid=812,fd=8))

Local Address 열을 꼼꼼히 보세요. 0.0.0.0과 [::]는 모든 IPv4, IPv6 주소라는 뜻입니다. 127.0.0.1:80은 로컬 연결만 받는다는 뜻이라 Cloudflare를 포함한 나머지는 모두 거부됩니다. 위 예시에서는 IPv6 80 포트에서 듣는 프로그램이 없습니다. 도메인에 프록시된 AAAA 레코드가 있다면 문제가 됩니다(원인 3).

그다음 서버 안에서 올바른 Host 헤더로 사이트를 요청해 봅니다.

curl -sI -H 'Host: www.example.com' http://127.0.0.1/
curl -skI --resolve www.example.com:443:127.0.0.1 https://www.example.com/

서비스가 죽어 있다면 재시작하기 전에 왜 죽었는지 먼저 보세요. 그렇지 않으면 또 죽습니다.

sudo journalctl -u nginx --since "2 hours ago" --no-pager | tail -50
sudo nginx -t
sudo journalctl -k --since today | grep -iE 'out of memory|oom-kill'

2단계: 바깥에서 닿는가

서버가 아닌 다른 기계에서 원 서버 IP로 직접 연결합니다. 원 서버 IP는 Cloudflare DNS 대시보드의 A 또는 AAAA 레코드 값입니다. 프록시를 켠 레코드는 공개 DNS에서 Cloudflare 주소만 나옵니다.

curl -v --resolve www.example.com:443:203.0.113.10 https://www.example.com/ -o /dev/null
nc -vz -w 5 203.0.113.10 80
curl: (7) Failed to connect to www.example.com port 443 after 4 ms: Connection refused

최신 curl은 “Couldn’t connect to server”라고 표시하기도 합니다. 중요한 것은 종료 코드 7과 거의 0에 가까운 시간입니다. 포트 점검으로 바깥에서 같은 테스트를 할 수 있습니다. 호스트명은 Cloudflare 주소로 풀리므로, 호스트명이 아니라 원 서버 IP를 넣으세요.

결과는 이렇게 읽습니다.

  • 금방 거부됨: 521을 재현한 것입니다. 듣는 프로그램이 없거나(1단계에서 이미 드러났을 것입니다) 방화벽이 연결을 거부하고 있습니다.
  • 시간 초과: 패킷이 버려지고 있습니다. Cloudflare 쪽에서 보면 521이 아니라 522입니다. 방화벽이 Cloudflare 대역만 허용하고 있다면 내 테스트 기계에서는 당연히 이렇게 나오므로 아무것도 알려 주지 않습니다. 이때는 서버 안에서 테스트하세요.
  • 내 기계에서는 되는데 Cloudflare는 계속 521을 받음: 무언가가 Cloudflare 주소를 내 주소와 다르게 다루고 있습니다. 원인 4입니다.

흔한 원인

1. 웹 서버가 멈췄고 다시 뜨지 않음

521의 대부분은 이것입니다. 흔한 경로는 이렇습니다. 설정을 고친 뒤 재시작했는데 실패함(nginx -t를 돌렸다면 잡혔을 것입니다), 커널 OOM killer가 PHP-FPM이나 앱을 죽임, systemd가 포기한 crash loop, 재부팅했는데 서비스를 enable하지 않았음.

해결: nginx -t나 저널에 나온 오류를 고치고 서비스를 시작한 뒤, sudo systemctl enable --now nginx로 재부팅 후에도 뜨게 하세요. OOM killer가 관련돼 있다면 메모리를 늘리거나 워커 수를 줄여야 합니다. 재시작만으로는 시간을 버는 것뿐입니다.

2. 듣고는 있지만 Cloudflare가 연결하는 곳이 아님

  • 앱이 0.0.0.0이 아니라 127.0.0.1에 바인딩됨. Node, Python, Go 서버에서 흔한 기본값입니다.
  • Docker 컨테이너의 포트를 publish하지 않음. publish돼 있으면 docker ps의 PORTS 열에 0.0.0.0:80->80/tcp가 보이고, 80/tcp만 있으면 안 된 것입니다.
  • 앱이 8080 같은 다른 포트로 옮겨 갔는데 앞단의 리버스 프록시를 없앰.

해결: 앱 앞에 80·443을 받는 리버스 프록시(nginx, Caddy, Apache)를 두거나, 앱을 올바른 포트의 공개 인터페이스에 바인딩하세요.

3. DNS에는 IPv6가 있는데 서버가 IPv6로 듣지 않음

Cloudflare에 프록시된 AAAA 레코드가 있으면 Cloudflare가 원 서버에 IPv6로 연결할 수 있습니다. listen 80;만 있고 listen [::]:80;이 없는 서버는 그 연결을 거부하고, 521이 어떤 요청에서는 나고 어떤 요청에서는 나지 않습니다.

해결: IPv6 listen 지시어를 추가하고 방화벽에서도 IPv6를 열거나, 서버가 IPv6를 서비스할 생각이 없다면 AAAA 레코드를 지우세요.

4. 방화벽이 Cloudflare IP 대역을 거부함

서버는 멀쩡하고 나에게는 잘 되는데, Cloudflare의 연결은 거부됩니다. Cloudflare 뒤에서는 모든 트래픽이 Cloudflare 주소에서 들어오기 때문에 실수로 막기가 쉽습니다.

  • fail2ban, CrowdSec 같은 도구가 요청이나 로그인 실패가 너무 많다는 이유로 Cloudflare 엣지 IP를 차단함. 그 엣지를 거치는 방문자만 521을 보고 나머지는 멀쩡합니다. 그래서 이런 521은 특정 지역이나 특정 통신사에서만 나는 경우가 많습니다.
  • 호스팅 관리 화면이나 보안 플러그인의 해외 IP 차단. Cloudflare 주소는 미국 회사 명의로 등록돼 있어서, “해외 IP 차단”을 켜면 국내 방문자라도 Cloudflare를 거쳐 오는 연결이 막힐 수 있습니다. 해외 접속 차단을 기본처럼 켜 두는 국내 호스팅과 서버에서 흔히 생깁니다.
  • 출발지 IP별 연결 수 제한(iptables connlimit 등)이 기준을 넘는 연결을 거부함. 몇 안 되는 Cloudflare IP가 방문자 전체를 실어 나르므로 금방 한도에 걸립니다.
  • 최근에 강화한 방화벽 규칙. 예를 들어 ufw 기본 거부 정책에 Cloudflare 대역의 80/443 허용을 넣지 않은 경우입니다.

차단이 521로 보일지 522로 보일지는 막는 방식에 달려 있습니다. 거부하는 규칙(ufw reject, iptables REJECT)은 즉시 응답하므로 521이 되고, 버리는 규칙(ufw deny, 대부분의 클라우드 보안 그룹)은 응답하지 않으므로 522가 됩니다.

해결: Cloudflare가 공개한 대역을 Cloudflare가 쓰는 포트에 대해 허용하고, 자동 차단 대상에서 빼세요. ufw라면 이렇게 합니다.

for ip in $(curl -s https://www.cloudflare.com/ips-v4) $(curl -s https://www.cloudflare.com/ips-v6); do
  sudo ufw allow proto tcp from "$ip" to any port 80,443
done

같은 목록을 https://api.cloudflare.com/client/v4/ips에서 JSON으로도 받을 수 있습니다. 목록은 가끔 바뀌므로 손으로 한 번 넣고 끝내지 말고 예약 작업으로 갱신하세요. fail2ban에서는 이 대역을 ignoreip에 넣습니다.

그다음 로그와 차단 도구가 Cloudflare가 아니라 방문자를 보게 하세요. nginx라면 realip 모듈로 설정합니다.

set_real_ip_from 173.245.48.0/20;   # Cloudflare 대역마다 한 줄씩
real_ip_header CF-Connecting-IP;

이것이 없으면 내가 거는 모든 차단이 Cloudflare에 걸리고, 그것이 다음 521이 됩니다.

5. DNS 레코드가 다른 서버를 가리킴

프록시된 A 레코드가 예전 서버, 재설치하면서 IP가 바뀐 VPS, 혹은 오타가 난 주소를 가리키고 있습니다. 그 주소의 무언가가 연결을 거부하거나, 내 사이트를 모르는 다른 기계가 응답합니다.

클라우드 VM에서는 ip addr로 보이는 주소가 사설 IP인 경우가 많습니다. 공인 IP는 클라우드 콘솔에서 확인하고, Cloudflare DNS의 레코드 값과 비교하세요.

해결: 레코드를 고치세요. IP가 자주 바뀐다면(집 서버, 유동 IP) 고정 A 레코드 대신 Cloudflare Tunnel이나 동적 DNS 갱신 도구를 쓰세요.

6. 났다 안 났다 함

간헐적인 521은 대개 원 서버 여러 대 중 하나가 고장 났거나(프록시된 A 레코드 여러 개, 로드밸런서 풀, 죽은 백엔드 하나), 부하가 걸리면 죽었다가 재시작되는 서비스이거나, 원인 4의 차단 도구가 엣지를 차단했다 풀었다 하는 경우입니다. 원 서버 IP를 하나씩 테스트하고, 서비스 재시작 횟수를 확인하세요.

systemctl show nginx -p NRestarts

고친 다음에

  • 페이지를 새로고침하세요. 오류 화면마다 새 Ray ID가 붙으므로, 새 Ray ID로 521이 또 나온다면 원 서버가 아직 거부하고 있는 것입니다.
  • 신고된 호스트명 하나만이 아니라 http://와 https:// 양쪽, 그리고 프록시를 거치는 모든 호스트명을 테스트하세요.
  • 원 서버 IP에 포트 점검을 다시 돌리고, 공개 URL에 HTTP 점검을 돌려 Cloudflare를 거쳐 사이트가 응답하는지 확인하세요.

비슷해 보이는 다른 오류

내 도메인에서 바로 확인

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