LabHub

네트워크 트러블슈팅 · HTTP 진단 · 이론

curl -w 로 느린 구간을 지목하기

LabHub 에서 이어서 보기

한 줄 요약

curl -w 의 타이밍 필드는 요청 하나를 이름 해석 / 연결 / TLS / 첫 바이트 / 전체 다섯 구간으로 쪼개 준다. "느리다" 는 신고를 구간 이름으로 바꾸는 것이 진단의 시작이다.

왜 이게 필요했나

"API 가 느려요" 는 정보가 거의 없는 문장이다. 이름 해석이 느린 것과 TLS 핸드셰이크가 느린 것과 백엔드 쿼리가 느린 것은 완전히 다른 문제이고 담당 팀도 다르다. 그런데 이 셋은 사용자 눈에는 똑같이 "느리다" 로 보인다.

어떻게 동작하나

한 줄이면 끝난다.

curl -o /dev/null -s -w \ 'dns=%{time_namelookup} conn=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total} code=%{http_code}\n' \ https://api.example.com/health

각 필드는 요청 시작으로부터의 누적 시간이다. 그래서 구간별 소요는 뺄셈으로 얻는다.

| 구간 | 계산 | 길면 의심할 곳 |
| --- | --- | --- |
| 이름 해석 | time_namelookup | 리졸버, search 도메인, ndots |
| TCP 연결 | time_connect - time_namelookup | 방화벽, 거리, SYN 재전송 |
| TLS | time_appconnect - time_connect | 인증서 체인, OCSP 조회, 프로토콜 협상 |
| 서버 처리 | time_starttransfer - time_appconnect | 애플리케이션, DB |
| 본문 전송 | time_total - time_starttransfer | 대역폭, 응답 크기 |

읽는 요령: connect 가 타임아웃까지 늘면 방화벽, appconnect 만 길면 TLS, appconnect 는 빠른데 ttfb 만 길면 애플리케이션이나 DB.

진단에 꼭 필요한 나머지 옵션들.

curl -v https://api.example.com/health                 # 요청/응답 헤더 전체curl --resolve api.example.com:443:10.0.1.50 https://api.example.com/healthcurl -H 'Host: api.example.com' http://10.0.1.10:8080/healthcurl -sD - -o /dev/null https://api.example.com/       # 헤더만curl -L --max-redirs 10 -v https://example.com 2>&1 | grep '< [Ll]ocation'

--resolveDNS 를 건드리지 않고 특정 이름을 특정 IP 로 보내게 한다. 배포 전에 새 서버를 실제 이름으로 시험할 때 필수다. -H 'Host:' 는 IP 로 직접 붙되 가상 호스트 라우팅은 원래대로 태우는 방법이다. LB 뒤 백엔드를 직접 두드릴 때 Host 를 안 맞추면 비교 자체가 무의미해진다.

-I 는 HEAD 요청을 보낸다. 서버가 HEAD 를 다르게 처리하거나 아예 막아 둔 경우 GET 과 결과가 달라지므로, 의심스러우면 -sD - -o /dev/null 로 GET 하면서 헤더만 보는 편이 안전하다.

자주 나오는 오진

502 를 "업스트림이 죽었다" 로 단정하는 것. 502 는 프록시가 업스트림에서 유효하지 않은 응답을 받았다는 뜻이다. 응답 헤더가 프록시 버퍼보다 크면 업스트림이 멀쩡한데도 502 가 난다. 그리고 502 에 타임아웃을 늘리는 것은 무효다 — 그건 504 의 처방이다.

만료일만 보고 인증서를 넘기는 것. 브라우저는 중간 인증서를 캐시하므로 개발자 PC 에서는 정상으로 보인다. 서버 대 서버 호출은 캐시가 없어 실패한다. 체인 개수를 세어 확인한다.

echo | openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts 2>/dev/null | grep -c 'BEGIN CERTIFICATE'

리다이렉트 루프를 애플리케이션에서 찾는 것. 보통은 TLS 종료 지점과 애플리케이션의 HTTPS 강제가 서로를 모르는 상황이다. X-Forwarded-Proto 를 전달하고 경계에서 덮어써야 한다.

리다이렉트 코드도 정확히 알아 두자. 302 는 POST 를 GET 으로 바꿔도 되고, 303 은 GET 을 강제하며, 307/308 은 메서드를 보존한다. 프로토콜 강제에는 308, 폼 제출 후 화면 전환에는 303 이 맞다.

현장에서 만나는 모습

"curl 은 되는데 브라우저만 실패합니다." 이 경우 서버가 아니라 브라우저 고유의 규칙을 봐야 한다. CORS 프리플라이트, SameSite 쿠키, 혼합 콘텐츠 차단, HSTS, HTTP/2 커넥션 병합(421) 이 후보다. curl 은 이 규칙들을 전혀 적용하지 않는다.

다음 실습에서 할 것

진단용 HTTP 서버를 띄우고 상태 코드·헤더·리다이렉트·가상 호스트·타이밍을 차례로 뽑는다. --resolve 로 DNS 없이 이름 접속을 만들어 보는 단계가 하이라이트다.