- Published on
AWS ALB 502·504 원인 - NLB·타임아웃·헬스체크
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
운영 중인 서비스에서 502 Bad Gateway 또는 504 Gateway Timeout가 뜨기 시작하면, 많은 팀이 곧바로 애플리케이션을 의심합니다. 하지만 AWS 로드 밸런서 계층에서는 “어디서부터 어디까지가 타임아웃의 책임 범위인지”, “헬스체크가 실제 트래픽 경로를 얼마나 반영하는지”에 따라 같은 증상이 전혀 다른 원인으로 나타납니다.
이 글은 ALB를 중심으로 502/504를 재현 가능한 형태로 분해하고, NLB를 앞단/뒷단에 두는 구성에서 특히 자주 터지는 함정(타임아웃 불일치, 연결 재사용 문제, 헬스체크 오판)을 기준으로 점검 순서를 제공합니다.
관련해서 인프라/네트워크 이슈가 애플리케이션 장애처럼 보이는 케이스를 다룬 글로는 GCP Cloud Run 503 해결 - VPC 커넥터·NAT 점검도 함께 참고하면, “게이트웨이 계층 오류를 어떻게 분해하는가”라는 관점이 비슷해 도움이 됩니다.
1) ALB 502 vs 504: 의미를 먼저 고정하기
1-1. ALB 504의 전형적인 의미
504는 대체로 “ALB가 백엔드(Target)로부터 제한 시간 내에 유효한 응답을 못 받았다”는 뜻입니다.
대표 원인:
- 백엔드 처리 시간이 길어 ALB의 idle timeout을 초과
- 백엔드가 응답을 시작하지 못하고 대기열에서 막힘(스레드/커넥션/DB)
- 네트워크 경로 문제로 응답이 돌아오지 않음(SG/NACL/라우팅)
1-2. ALB 502의 전형적인 의미
502는 “ALB가 백엔드와 연결/프로토콜 관점에서 정상적인 응답을 받지 못했다”에 가깝습니다.
대표 원인:
- 백엔드가 연결을 즉시 끊음(
RST) - 백엔드가 잘못된 HTTP 응답을 반환(헤더/프레이밍)
- TLS 설정 불일치(HTTPS 리스너, 백엔드 인증서, SNI)
- HTTP/2, gRPC 같은 프로토콜 기대치 불일치
핵심은 504가 “시간”, 502가 “형식/연결”에 더 가깝다는 점입니다. 물론 현실에서는 둘이 섞여 보이지만, 분류부터 고정하면 로그/메트릭에서 찾아야 할 지점이 명확해집니다.
2) 가장 흔한 뿌리 원인: 타임아웃 체인 불일치
ALB 장애의 상당수는 “각 계층의 타임아웃이 서로 다르게 설정되어” 특정 구간에서 끊기며 발생합니다. 아래는 흔한 체인 예시입니다.
- 클라이언트(브라우저/SDK) 타임아웃
- CDN 또는 WAF 타임아웃
- ALB idle timeout
- 백엔드(서버) keep-alive / request timeout
- 애플리케이션 내부 타임아웃(HTTP client, DB, 외부 API)
2-1. ALB idle timeout을 기준점으로 잡기
ALB의 대표 설정이 idle timeout입니다. 장시간 응답이 없으면 연결을 끊습니다. 긴 요청(리포트 생성, 대용량 export, LLM 호출 등)이 존재한다면 아래 중 하나가 필요합니다.
- 비동기화: 요청은
202로 즉시 반환하고 작업은 큐로 넘기기 - 스트리밍/청크 응답: 중간중간 바이트를 흘려 idle로 판단되지 않게 하기
- ALB idle timeout 조정: 단, 근본 해결이 아니라 “증상 완화”가 될 수 있음
2-2. “ALB는 괜찮은데 클라이언트가 먼저 끊는” 케이스
ALB 설정을 늘려도 504가 계속이면, 실제로는 더 앞단(클라이언트나 CDN)이 먼저 끊고 있을 수 있습니다. 이때 서버는 계속 작업하다가 응답을 쓰는 순간 Broken pipe류 에러를 남기기도 합니다.
점검 방법:
- 클라이언트/프록시의 타임아웃 값 확인
- 애플리케이션 로그에서 “클라이언트 연결 종료” 흔적 확인
- ALB 액세스 로그의
target_processing_time와request_processing_time비교
3) ALB 액세스 로그로 502/504를 “숫자로” 분해하기
ALB 액세스 로그를 켜면, 감으로 추측하던 것을 수치로 쪼갤 수 있습니다. 특히 아래 필드가 핵심입니다.
elb_status_codetarget_status_coderequest_processing_timetarget_processing_timeresponse_processing_time
예를 들어 elb_status_code가 504이고 target_processing_time이 ALB idle timeout 근처로 수렴한다면, 백엔드가 늦는 패턴일 가능성이 큽니다. 반대로 502인데 target_status_code가 비어 있거나 -로 나오면, 백엔드에서 HTTP 응답을 만들기도 전에 연결이 깨졌을 수 있습니다.
3-1. Athena로 5분 내 원인 후보 좁히기
S3에 ALB 로그를 쌓고 Athena로 질의하면, 장애 시간대의 분포를 바로 볼 수 있습니다.
-- elb_status_code별 빈도
SELECT elb_status_code, count(*) AS cnt
FROM alb_logs
WHERE time BETWEEN timestamp '2026-02-24 10:00:00' AND timestamp '2026-02-24 10:10:00'
GROUP BY elb_status_code
ORDER BY cnt DESC;
-- 504에서 target_processing_time 상위 요청
SELECT request_url, target_processing_time
FROM alb_logs
WHERE elb_status_code = 504
ORDER BY target_processing_time DESC
LIMIT 50;
이 결과가 특정 엔드포인트에 몰려 있으면 애플리케이션/DB 병목으로, 엔드포인트가 다양하게 퍼져 있으면 인프라(커넥션/스케일/네트워크) 쪽으로 무게가 이동합니다.
4) 헬스체크가 “정상”인데 실트래픽이 죽는 이유
헬스체크는 “트래픽 경로를 대표하는 최소 조건”이어야 합니다. 그렇지 않으면 헬스체크는 통과하지만 실요청은 실패하는 전형적인 상황이 나옵니다.
4-1. 헬스체크 엔드포인트가 너무 가벼운 경우
/health가 단순히 200 OK만 반환하면, DB 커넥션 고갈이나 외부 의존성 장애를 전혀 반영하지 못합니다.
권장:
- 최소한 DB ping 또는 핵심 의존성의 간단한 read를 포함
- 단, 너무 무거우면 헬스체크 자체가 부하가 됨
4-2. 타임아웃/임계값이 현실과 안 맞는 경우
헬스체크 timeout이 너무 짧으면 순간적인 GC/IO 스파이크에도 타겟이 unhealthy로 흔들립니다. 반대로 너무 길면 실제 장애 감지가 늦습니다.
운영 팁:
timeout은 P99보다 약간 여유healthy_threshold와unhealthy_threshold로 “몇 번 연속 실패/성공”을 안정화
4-3. 헬스체크는 HTTP, 실트래픽은 gRPC/HTTP2인 경우
ALB에서 gRPC를 쓰면서 헬스체크는 HTTP 200로만 확인하면, 프로토콜 계층 문제를 놓칠 수 있습니다. 특히 백엔드가 HTTP/2 설정이 불완전하면 실요청만 502로 터질 수 있습니다.
5) NLB와 함께 쓸 때 자주 터지는 502/504 패턴
“ALB 앞에 NLB”, “ALB 뒤에 NLB”, 또는 “NLB로 TCP 패스스루 후 내부에서 ALB 역할” 같은 조합은 요구사항(고정 IP, TLS 패스스루, PrivateLink 등) 때문에 선택되지만, 디버깅 난이도가 급상승합니다.
5-1. 타임아웃 기준이 바뀐다
NLB는 L4(주로 TCP) 관점이고, ALB는 L7(HTTP) 관점입니다. 이때 “어느 계층이 먼저 끊는지”가 바뀌면 겉으로 보이는 코드는 달라집니다.
- NLB에서 연결이 끊기면 ALB까지 도달하지 못해 ALB 로그에 흔적이 약할 수 있음
- ALB에서 끊기면
elb_status_code로 관찰 가능
점검 순서:
- 클라이언트 관측(응답 코드/지연)
- ALB 액세스 로그에서 동일 요청이 보이는지 확인
- ALB에 흔적이 없다면 NLB 타겟 그룹/Flow Log/VPC Reachability로 이동
5-2. Keep-alive 및 연결 재사용과의 충돌
NLB는 TCP 연결을 오래 유지하고 재사용합니다. 백엔드(또는 중간 프록시)가 keep-alive 정책이 짧거나, 특정 시간 이후 연결을 닫아버리면 “재사용하려던 연결이 죽어있어서” 502 비슷한 증상이 발생할 수 있습니다.
대응:
- 백엔드 서버의 keep-alive timeout을 상향
- 클라이언트(또는 프록시)의 커넥션 풀 재시도 정책 점검
- 중간 계층에서 커넥션 드레이닝/재사용 정책 확인
5-3. Proxy Protocol 사용 시 헤더/파싱 문제
NLB에서 Proxy Protocol을 켜고, 백엔드가 이를 해석하지 못하면 애플리케이션은 첫 줄부터 깨진 요청으로 보고 연결을 끊을 수 있습니다. 이 경우 502로 관측될 수 있습니다.
체크:
- NLB 타겟 그룹의 Proxy Protocol v2 설정 여부
- 백엔드(Envoy, Nginx, HAProxy, 애플리케이션 서버)가 Proxy Protocol을 지원/활성화했는지
Nginx 예시 설정(Proxy Protocol을 받는 리스너):
server {
listen 80 proxy_protocol;
set_real_ip_from 10.0.0.0/8;
real_ip_header proxy_protocol;
location / {
proxy_pass http://app_upstream;
}
}
6) 헬스체크는 통과, 트래픽은 504: 백엔드 병목의 전형
ALB 504가 늘고, 타겟은 healthy로 보일 때 흔한 원인은 “백엔드가 살아는 있는데 너무 느린 상태”입니다. 특히 다음이 자주 원인입니다.
- DB 커넥션 풀 고갈
- 외부 API 지연으로 스레드가 대기
- 파일/네트워크 IO 병목
- 컨테이너 CPU throttling, 메모리 압박으로 인한 GC
DB 병목의 전형적인 케이스는 데드락/락 경합입니다. 증상이 504로 나타나기도 하므로, DB 쪽이 의심되면 PostgreSQL 데드락(40P01) 원인·해결 9단계도 함께 점검하는 것이 좋습니다.
6-1. 애플리케이션 레벨 타임아웃을 “ALB보다 짧게”
중요한 운영 원칙 중 하나는 “상위 계층 타임아웃보다 하위 계층 타임아웃이 더 짧아야 한다”입니다. 그래야 애플리케이션이 먼저 실패를 결정하고(에러 응답/서킷 브레이커), ALB가 504를 뱉기 전에 통제 가능한 형태로 종료할 수 있습니다.
Node.js(Express)에서 서버 타임아웃을 명시하는 예시:
import http from 'http';
import app from './app.js';
const server = http.createServer(app);
// 요청 전체 타임아웃(예: 55초)
server.requestTimeout = 55_000;
// keep-alive 유휴 타임아웃(예: 60초)
server.keepAliveTimeout = 60_000;
// 헤더 수신 타임아웃(예: 65초)
server.headersTimeout = 65_000;
server.listen(3000);
이 값을 ALB idle timeout과 “의도적으로” 맞추거나(혹은 더 짧게) 설계하면, 장애 시 에러가 504로 퍼지는 것을 줄일 수 있습니다.
7) 502가 간헐적일 때: TLS/프로토콜/리스너 설정 점검
간헐적 502는 다음 패턴이 많습니다.
- 일부 타겟만 설정이 다름(AMI/컨테이너 이미지 차이)
- 특정 AZ에서만 네트워크 경로 문제
- TLS 핸드셰이크 실패가 특정 클라이언트에서만 발생
7-1. 타겟 그룹의 프로토콜과 백엔드 실제 프로토콜 일치
ALB 리스너가 HTTPS이고 타겟 그룹 프로토콜이 HTTP인 것은 흔한 구성입니다. 문제는 백엔드가 실제로는 HTTPS만 받는데 타겟 그룹이 HTTP로 설정되어 있거나, 반대로 HTTP만 받는데 HTTPS로 설정된 경우입니다.
이때는 연결은 되더라도 응답이 깨지거나 즉시 종료되어 502로 보일 수 있습니다.
7-2. gRPC/HTTP2 사용 시 ALB 설정 확인
ALB의 타겟 그룹 프로토콜 버전(HTTP1/HTTP2/gRPC)이 백엔드와 맞지 않으면, 정상 응답을 못 받아 502가 발생할 수 있습니다.
8) 실전 트러블슈팅 체크리스트(우선순위)
장애가 났을 때 “무엇부터 확인할지”를 고정해두면 평균 복구 시간이 줄어듭니다.
8-1. 1단계: 관측 지점 확정
- 같은 시각에
5xx가 특정 경로에 집중되는지 - ALB 액세스 로그가 남는지(남지 않으면 더 앞단 또는 네트워크)
elb_status_code와target_status_code조합
8-2. 2단계: 타임아웃 체인 매핑
- ALB idle timeout
- 백엔드 서버의 request/keep-alive timeout
- 애플리케이션 내부(외부 API/DB) 타임아웃
이때 “가장 바깥 타임아웃”이 아니라 “가장 안쪽 타임아웃”부터 짧게 설계했는지 확인합니다.
8-3. 3단계: 헬스체크가 현실을 반영하는지
- 헬스체크 경로가 핵심 의존성을 반영하는지
- timeout/threshold가 흔들림을 유발하지 않는지
- 실트래픽과 프로토콜이 같은지
8-4. 4단계: NLB 조합 특이점 점검
- Proxy Protocol 사용 여부
- keep-alive/커넥션 재사용 정책 충돌
- ALB 로그에 요청이 아예 안 보이면 NLB/VPC 레벨로 이동
9) 장애를 줄이는 설계 팁: “빠르게 실패하고, 길게 작업은 분리”
ALB 504는 본질적으로 “동기 요청으로 너무 많은 일을 한다”는 신호인 경우가 많습니다.
- 긴 작업은 큐(SQS), 워커, 배치로 분리
- API는
202와 작업 조회 엔드포인트 제공 - 클라이언트는 폴링 또는 SSE/WebSocket으로 진행 상태 수신
- DB/외부 API 호출에는 타임아웃과 재시도(지수 백오프) 정책을 명확히
재시도 설계 관점은 레이트리밋 대응과도 유사합니다. 외부 의존성 호출이 많은 서비스라면 Anthropic Claude 429 레이트리밋 재시도 설계법처럼 “어디까지 재시도하고 어디서 실패할지”를 명시하는 것이 504를 줄이는 데도 직접적으로 도움이 됩니다.
마무리
ALB 502/504는 단순히 “서버가 죽었다”가 아니라, 타임아웃 체인과 프로토콜 기대치, 헬스체크 설계가 만들어내는 결과입니다.
504는 우선 타임아웃과 백엔드 지연을 수치로 확인하고502는 연결/프로토콜/TLS/Proxy Protocol 같은 “형식”을 의심하며- NLB가 끼면 L4 특성과 커넥션 재사용의 함정을 별도로 점검하세요.
ALB 액세스 로그를 켜고 request_processing_time, target_processing_time를 기준으로 분해하는 순간부터, 502/504는 “감”이 아니라 “데이터”로 다룰 수 있습니다.