네트워크·보안
Cloudflare 520·522·525가 번갈아 뜰 때 NAS가 아니라 공유기 구간을 의심한 근거
Cloudflare 5xx가 섞여 나올 때 NAS 직결 측정, 서버 로그, ICMP 손실, 스캐너 몰림 시각을 비교해 공유기 인바운드 구간의 세션 단위 끊김으로 좁히고 20초 프로브로 확인한 기록입니다.
rapsso.com 모니터가 밤마다 5~10분 간격으로 다운과 복구를 반복했습니다. 평소엔 하루 0~1번이던 게 하루 26번으로 늘었습니다. 에러도 한 가지가 아니었습니다. Cloudflare가 돌려주는 520, 522, 525에 48초 타임아웃까지 섞여 나왔습니다.
처음엔 당연히 NAS를 의심했습니다. PHP가 막혔나, 웹 서버가 죽었나. 그런데 하나씩 확인해 보니 NAS는 멀쩡했고, 문제는 Cloudflare에서 집 공유기를 거쳐 NAS로 들어오는 구간에만 있었습니다. 그렇게 판단한 근거를 순서대로 적습니다.
세 에러가 섞여 나온다는 것부터 힌트입니다
| 코드 | Cloudflare 기준 의미 |
|---|---|
| 520 | 오리진이 빈 응답을 주거나 연결을 끊음 |
| 522 | 오리진에 TCP 연결 자체가 안 됨(시간 초과) |
| 525 | 오리진과 TLS 핸드셰이크 실패 |
서버 프로그램이 느려서 생기는 문제라면 보통 한 종류가 반복됩니다. 연결 단계(522), 암호화 단계(525), 응답 단계(520)에서 골고루 끊긴다는 건, 중간 어딘가에서 연결 하나가 통째로 끊기고 있다는 쪽에 가깝습니다. 방화벽이나 공유기처럼 연결 상태를 기억하는 장비에서 자주 보이는 모양입니다.
NAS 쪽 증거: 직결은 매번 정상, 로그는 0건
같은 시간대에 NAS 안에서 Cloudflare를 거치지 않고 자기 웹 서버로 바로 요청을 보내 봤습니다. curl의 --resolve로 도메인은 그대로 두고 접속 주소만 바꾸면 됩니다.
curl -k -s -o /dev/null -w '%{http_code} %{time_starttransfer}\n' \
--resolve rapsso.com:443:<NAS 공인 주소> https://rapsso.com/
매번 200, 17~25ms였습니다. nginx 에러 로그와 Apache 로그에도 이 사이트에 대한 업스트림 타임아웃 기록은 0건이었습니다. 웹 서버는 요청이 오기만 하면 빠르게 답하고 있었습니다.
또 하나 눈에 띈 게 있습니다. 모니터링 도구에는 다른 사이트도 여럿 등록돼 있었는데 다운이 찍힌 건 rapsso.com뿐이었습니다. 알고 보니 다른 사이트들은 Cloudflare를 거치지 않았고, 모니터가 NAS와 같은 집 안에서 돌기 때문에 공유기 안쪽으로 바로 돌아 들어가고 있었습니다. 즉 바깥에서 공유기로 들어오는 구간을 실제로 지나가는 모니터는 rapsso.com 하나였습니다.
회선 손실도 아니었습니다
회선이 불안하면 ping 손실이 보여야 합니다. 구간별로 재 봤습니다.
| 대상 | 손실 |
|---|---|
| 집 공유기 | 0% |
| 통신사 첫 게이트웨이 | 1% |
| 8.8.8.8 | 1% |
| Cloudflare 엣지 | 3% |
이 정도 손실로는 5분마다 연결이 통째로 끊기는 걸 설명하기 어렵습니다. 패킷이 조금씩 빠지는 게 아니라 특정 연결만 골라서 끊기는, 세션 단위 문제로 봤습니다. 참고로 가정용 회선에서 Cloudflare로 가는 경로가 국내가 아니라 홍콩·오사카·도쿄 데이터센터로 돌아 나가고 있었습니다.
다운 시각이 스캐너 몰림과 겹쳤습니다
같은 시간대 nginx 에러 로그를 시간당 줄 수로 세 보니 22~23시에 5,700~5,900줄이었습니다. 클라우드 업체 IP 대역에서 스캐너가 NAS의 IP로 직접 들어와 PHP 사이트들을 분당 수백 번씩 두드리고 있었습니다.
분 단위로 몰린 시각이 22:28, 22:46, 22:50, 23:09~13, 23:16, 23:24, 23:39, 23:49였고, 모니터의 다운 시각과 대부분 겹쳤습니다. 스캐너가 몰리면 공유기가 기억해야 하는 연결 수가 순간적으로 늘어납니다. 가정용 공유기의 세션 표나 DoS 방어 기능이 이때 정상 연결까지 같이 끊는다고 보면 증상이 다 맞습니다. 다만 공유기 내부 로그는 보지 못해서, 여기까지는 정황입니다.
판단을 굳히려고 20초 프로브를 붙였습니다
모니터 하나로는 어느 구간인지 안 나눠지니, NAS에 20초마다 네 갈래를 동시에 재서 CSV로 남기는 셸 스크립트를 붙였습니다.
while true; do
F=$DIR/probe_$(date +%Y%m%d).csv
T=$(date +%H:%M:%S)
CF=$(curl -4 -sS -o /dev/null -m 60 \
-w '%{http_code},%{time_connect},%{time_starttransfer},%{errormsg}' https://rapsso.com/)
DR=$(curl -k -s -o /dev/null -m 20 --resolve rapsso.com:443:<NAS 공인 주소> \
-w '%{http_code},%{time_starttransfer}' https://rapsso.com/)
CE=$(curl -k -s -o /dev/null -m 15 -w '%{time_connect}' https://<Cloudflare 엣지 IP>/)
GG=$(curl -s -o /dev/null -m 15 -w '%{time_connect}' https://www.google.com/generate_204)
NS=$(netstat -tan | awk '...ESTABLISHED, SYN_RECV, TIME_WAIT 개수...')
echo "$T,$CF,$DR,$CE,$GG,$NS" >> $F
sleep 20
done
네 갈래는 Cloudflare 경유, NAS 직결, Cloudflare 엣지까지의 연결 시간, 구글까지의 연결 시간입니다. 마지막에 NAS의 TCP 연결 상태 개수도 같이 적습니다. 경유만 실패하고 나머지가 멀쩡하면 Cloudflare에서 NAS로 들어오는 구간 문제라는 게 한 줄로 보입니다.
실제로 첫날 밤 기록이 그랬습니다.
time,cf_code,cf_connect,cf_ttfb,...,direct_code,direct_ttfb,...
00:08:23,525,0.066,44.79,,200,0.023,...
00:10:10,520,0.064,53.88,,200,0.024,...
00:12:52,000,0.691,0.000,Operation timed out after 60001 ms,200,0.022,...
00:22:18,520,0.774,19.77,,200,0.023,...
Cloudflare 경유가 525, 520, 타임아웃으로 실패하는 같은 순간에 직결은 전부 200, 20ms대였습니다.
경로를 바꾼 뒤
9월 22일 새벽에 rapsso.com의 Cloudflare 프록시를 끄고 DNS만 쓰도록 바꿨습니다. 그날 경유 실패 9건은 전부 0시~1시대에 몰려 있었고(그중 1건은 직결도 같이 503이 난 순간이었습니다), 경로가 바뀐 뒤로는 사라졌습니다.
| 날짜 | 측정 수 | 경유 실패 | 평균 응답 시작 |
|---|---|---|---|
| 9월 22일 | 4,156 | 9 (전부 0시~1시) | 0.277초 |
| 9월 23~26일 | 하루 약 4,190 | 0 | 0.182~0.190초 |
| 9월 27일(정오까지) | 2,129 | 0 | 0.192초 |
솔직히 적어 두면, 프록시를 끈 뒤의 0건은 조건이 달라졌다는 점을 감안해야 합니다. 이제 NAS가 자기 주소로 요청하면 공유기 안쪽에서 돌아 나오기 때문에, 바깥에서 들어오는 구간을 온전히 재는 게 아닙니다. 바깥 관점의 모니터가 따로 없다는 건 아직 남은 숙제입니다.
정리하면, Cloudflare 5xx가 여러 종류로 섞여 나오면 서버부터 뜯기 전에 직결로 같은 순간을 재 보는 게 가장 빠릅니다. 직결이 멀쩡하면 서버가 아니라 그 앞 구간입니다.
확인한 공식 자료
구현 과정에서 판단 기준을 교차 확인한 공식 문서입니다. 글의 사례와 결론은 운영자가 직접 겪은 작업을 바탕으로 작성했습니다.