- Published on
Cloudflare 520·521, Nginx·ALB 로그로 30분 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 죽은 것도 아닌데 Cloudflare에서 520(Web server returned an unknown error) 또는 521(Web server is down) 이 튀면, 체감상 가장 답답한 점은 “Cloudflare 쪽 문제인가? 오리진 문제인가?”가 즉시 구분되지 않는다는 것입니다.
이 글은 Nginx(오리진) 로그 + AWS ALB 로그 + Cloudflare 이벤트(레이/요청ID) 를 한 타임라인으로 맞춰서 30분 내에 원인을 좁히는 실전 절차를 정리합니다. 특히 520/521은 “원인 후보가 넓어 보이지만, 로그를 제대로 보면 의외로 금방 좁혀지는” 케이스가 많습니다.
1) 520과 521의 차이: ‘연결’이냐 ‘응답’이냐
521: Cloudflare가 오리진에 TCP 연결을 못 함
- Cloudflare가 오리진(또는 ALB/리버스프록시)에 접속하려 했지만 연결 자체가 실패한 경우가 많습니다.
- 대표 원인
- 방화벽/보안그룹에서 Cloudflare IP 대역 차단
- 오리진이 특정 국가/ASN 차단, WAF 룰로 차단
- 오리진이 Listen 안 함(프로세스 다운), 포트 미오픈
- ALB 타겟 헬스체크 실패로 모든 타겟이 Unhealthy
520: Cloudflare는 연결했는데 ‘이상한’ 응답을 받음
- 연결은 됐지만 Cloudflare가 정상 HTTP 응답으로 처리하지 못한 경우입니다.
- 대표 원인
- 오리진이 빈 응답, 비정상 헤더, 조기 연결 종료
- 업스트림(앱) 크래시로 Nginx가 502/504/499 유사 상황을 만들거나, 응답 도중 끊김
- HTTP/2, keepalive, proxy buffering, timeout 조합 문제
- 오리진이 너무 느려 Cloudflare 타임아웃/리셋
> 참고: 520은 ‘Cloudflare가 정의한 Unknown error’라서, 실제로는 오리진에서 5xx/연결종료/리셋 등 다양한 형태로 나타납니다. 그래서 Nginx/ALB 로그와 매칭이 핵심입니다.
2) 30분 진단 로드맵(체크리스트)
아래 순서대로 보면 “어느 구간에서 끊겼는지”가 빠르게 정리됩니다.
Step A (3분): Cloudflare 이벤트에서 Ray ID/시간/엣지 POP 확보
- Cloudflare 대시보드(또는 에러 페이지)에서 Ray ID 확인
- 발생 시간(UTC/로컬), 요청 경로, 사용자 IP(가능하면), 응답 코드(520/521)
Step B (7분): ALB가 있었는지부터 확인
- Cloudflare → (ALB) → Nginx → App 구조인지
- ALB가 있다면 ALB access log가 1차 진실에 가깝습니다.
Step C (10분): Nginx access/error 로그에서 같은 시각의 요청이 있었는지
- Nginx access 로그에 요청이 없다 → 521 가능성 상승(방화벽/라우팅/ALB 전단)
- access 로그는 있는데 status/bytes_sent가 이상 → 520 가능성 상승(응답 중단/업스트림 문제)
Step D (10분): “연결 실패 vs 업스트림 실패”로 갈라치기
- 연결 실패(521 계열): SG/NACL/WAF/Cloudflare IP allowlist/리스너/헬스체크
- 업스트림 실패(520 계열): timeout/keepalive/버퍼링/앱 크래시/리셋
3) Nginx 로그 포맷을 ‘진단형’으로 바꾸기(필수)
기본 combined 로그만으로는 520/521을 빠르게 못 잡습니다. 아래처럼 업스트림/요청시간/연결상태를 남겨야 합니다.
Nginx access_log 포맷 예시
log_format diagnose '$time_iso8601 '
'remote=$remote_addr cfip=$http_cf_connecting_ip '
'host=$host req="$request" status=$status bytes=$body_bytes_sent '
'rt=$request_time urt=$upstream_response_time '
'uaddr=$upstream_addr ustatus=$upstream_status '
'ref="$http_referer" ua="$http_user_agent" '
'rid=$request_id cf_ray=$http_cf_ray';
access_log /var/log/nginx/access.log diagnose;
error_log /var/log/nginx/error.log warn;
cfip=$http_cf_connecting_ip: Cloudflare 뒤 실제 클라이언트 IPcf_ray=$http_cf_ray: Cloudflare Ray를 헤더로 받는 경우 매칭이 매우 쉬워집니다.urt/ustatus/uaddr: 업스트림이 죽었는지, 타임아웃인지, 특정 타겟만 문제인지 확인
> $request_id는 Nginx 빌드/설정에 따라 없을 수 있습니다. 없다면 map/set으로 UUID를 만들거나, 앱 레벨에서 correlation-id를 넣어도 됩니다.
4) 521을 10분 내로 잡는 법: “로그가 없다”는 로그
4-1) Nginx access 로그에 요청이 아예 없다
- Cloudflare가 오리진까지 못 온 것입니다.
- 이때는 Nginx 튜닝이 아니라 네트워크/보안을 봐야 합니다.
즉시 확인할 것
- 오리진(또는 ALB) 보안그룹/방화벽에서 Cloudflare IP 대역 허용
- Cloudflare는 고정 IP가 아니라 공식 IP 대역 리스트로 관리해야 합니다.
- 오리진 포트 리스닝
sudo ss -lntp | grep -E ':80|:443'
- 로컬에서 Nginx 자체 응답 확인(오리진 내부)
curl -sv http://127.0.0.1/health
curl -sv https://127.0.0.1/health -k
- ALB가 있다면 타겟 헬스 상태
- 특정 AZ만 Unhealthy면 521이 간헐적으로 보일 수 있습니다.
4-2) ALB 로그에서 target_status_code가 비어있거나 연결 실패
ALB access log에서 타겟에 연결을 못 하면 target_status_code가 -로 찍히거나, error_reason(추가 필드/버전에 따라)가 힌트를 줍니다.
5) 520을 20분 내로 잡는 법: “연결은 됐는데 응답이 깨짐”
520은 대개 아래 3가지로 수렴합니다.
5-1) 업스트림 타임아웃/지연: upstream timed out
Nginx error 로그에서 가장 흔한 패턴입니다.
sudo grep -E "upstream timed out|recv\(\) failed|connection reset" /var/log/nginx/error.log | tail -n 50
upstream timed out (110: Connection timed out) while reading response header from upstream- 앱 서버가 느리거나 멈춤(GC, DB lock, 외부 API 지연)
- Nginx
proxy_read_timeout이 짧음 - keepalive/커넥션 풀 고갈
Nginx 타임아웃 기본 정리(예시)
location / {
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
send_timeout 30s;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://app_upstream;
}
- 스트리밍/롱폴링이면
proxy_read_timeout을 늘리되, Cloudflare/ALB idle timeout도 같이 맞춰야 합니다. - 스트리밍 계열 장애 패턴은 Nginx/업스트림 타임아웃과 결합해 520/504로 나타나기도 합니다. 관련해서는 OpenAI Responses API 스트리밍 끊김 타임아웃 완전 복구 가이드에서 “중간 프록시에서 끊기는” 전형적인 증상을 참고할 만합니다.
5-2) 연결 리셋/조기 종료: connection reset by peer
- 앱이 크래시하거나 워커가 강제 종료되면 Nginx는 업스트림에서 RST를 받습니다.
- 이때 Cloudflare는 “이상한 응답”으로 보고 520으로 뭉개서 보여줄 수 있습니다.
Nginx error 로그 예시 패턴
recv() failed (104: Connection reset by peer) while reading response header from upstreamupstream prematurely closed connection while reading response header from upstream
이 경우 앱 로그(예: Gunicorn/Uvicorn, JVM, Node)에서 같은 시각의 재시작/OOM/Crash를 찾는 게 빠릅니다. 쿠버네티스라면 OOMKilled도 유력하고, 그 진단은 Kubernetes OOMKilled 진단과 메모리 누수 추적 실전 체크리스트가 그대로 적용됩니다.
5-3) Cloudflare/ALB/Nginx 타임아웃 불일치
프록시가 여러 겹이면 “앞단은 더 오래 기다리는데 뒷단이 먼저 끊는” 혹은 그 반대가 자주 발생합니다.
- Cloudflare ↔ 오리진: 특정 제한(플랜/기능에 따라 상이)
- ALB:
idle timeout기본 60s(설정 가능) - Nginx:
proxy_read_timeout,send_timeout - 앱 서버: keepalive, worker timeout(Gunicorn
timeout등)
증상 조합 예시
- ALB idle timeout 60s, 앱 응답 70s → ALB가 먼저 끊고 Nginx/Cloudflare는 520/504 류로 관측
- Nginx
proxy_read_timeout30s, Cloudflare는 더 기다림 → Nginx가 504/499를 만들고 Cloudflare는 520으로 표현
6) ALB 로그로 “어디서 지연됐는지” 분해하기
ALB access log의 핵심은 보통 아래 3개 시간입니다.
request_processing_time: ALB가 요청을 처리/라우팅하는 시간target_processing_time: 타겟(오리진)이 처리한 시간response_processing_time: 응답을 클라이언트로 보내는 시간
Athena로 520/521 시간대만 뽑는 쿼리(예시)
(로그 테이블 스키마는 환경마다 다르니 필드명은 맞춰야 합니다.)
SELECT
time,
elb_status_code,
target_status_code,
request_processing_time,
target_processing_time,
response_processing_time,
client_ip,
target_ip,
request_url
FROM alb_logs
WHERE time BETWEEN timestamp '2026-02-23 01:00:00' AND timestamp '2026-02-23 01:30:00'
AND (elb_status_code >= 500 OR target_status_code >= 500 OR target_status_code IS NULL)
ORDER BY time DESC
LIMIT 200;
해석 팁
target_processing_time이 튀면: 앱/DB/외부 API 병목target_status_code가-/NULL이면: 타겟 연결 실패(521 계열 가능)elb_status_code는 200인데 클라이언트는 520을 봄: Cloudflare ↔ ALB 구간에서 리셋/프로토콜 문제 가능(희귀하지만 체크)
7) “30분 진단”을 가능하게 하는 상관관계 키(Correlation)
520/521을 빨리 끝내려면, 서로 다른 레이어의 로그를 한 줄로 꿰는 키가 필요합니다.
추천 키 우선순위
- Cloudflare Ray ID (
CF-RAY) - X-Request-ID(엣지/ALB/NGINX/앱 공통)
- 시간(초 단위) + 경로 + 클라이언트 IP(Cloudflare Connecting IP)
Nginx가 CF-RAY를 로그에 남기면, Cloudflare에서 본 에러 한 건을 그대로 오리진 로그에서 역추적할 수 있어 진단 시간이 급감합니다.
8) 자주 나오는 원인별 “즉시 처방” 요약
521 즉시 처방
- 오리진/ALB Security Group에 Cloudflare IP 대역 allowlist
- WAF/Fail2ban/GeoIP 차단 룰에서 Cloudflare를 오탐 차단하지 않는지 확인
- ALB 타겟 헬스체크 경로/성공코드/타임아웃 재점검
520 즉시 처방
- Nginx error 로그에서
upstream timed out,prematurely closed,reset by peer를 먼저 분류 - ALB
target_processing_time으로 앱 지연인지 연결 문제인지 구분 - 타임아웃 값을 Cloudflare/ALB/Nginx/앱 순서대로 일관되게 조정
추가로, 간헐 5xx/타임아웃을 “재현-관측-해결” 패턴으로 잡는 접근은 OpenAI Responses API 504 Timeout 재현·해결 글의 구조(재현 조건 만들기 → 타임라인으로 병목 찾기)와 매우 유사합니다.
9) 마무리: 520·521은 ‘원인’이 아니라 ‘증상’이다
Cloudflare 520/521은 Cloudflare가 친절하게 원인을 말해주는 에러가 아니라, 엣지에서 관측된 증상 코드에 가깝습니다. 그래서 해결의 핵심은 “Cloudflare 화면”이 아니라 오리진과 로드밸런서 로그를 같은 축으로 정렬하는 것입니다.
정리하면, 30분 안에 끝내는 핵심은 딱 세 가지입니다.
- Nginx에 업스트림/시간/CF-RAY가 찍히는 진단형 로그 포맷을 갖춘다.
- ALB 로그에서 target 연결/처리 시간을 분해해 병목 위치를 확정한다.
- 521은 네트워크/차단, 520은 업스트림/타임아웃/조기종료로 큰 가지부터 나눈다.
이 세 가지만 지켜도 “감으로 튜닝”하는 시간이 줄고, 다음 장애 때는 같은 절차로 더 빨리 복구할 수 있습니다.