- Published on
AWS ALB 502/504 급증 - 타임아웃 7곳 점검
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
ALB(Application Load Balancer) 뒤의 서비스가 멀쩡해 보이는데도 갑자기 502/504가 치솟는 상황은 보통 “어딘가에서 먼저 끊었다”는 신호입니다. 중요한 포인트는 타임아웃은 한 군데가 아니라 여러 계층에 존재하고, 이 값들이 서로 어긋날 때 오류가 폭발적으로 나타난다는 점입니다.
이 글에서는 ALB 기준으로 502/504가 급증할 때 타임아웃 관련 7곳을 우선순위대로 점검하는 방법을 정리합니다. 각 항목은 “증상 → 확인 방법 → 권장 설정/해결” 흐름으로 설명합니다.
관련해서 네트워크/타임아웃 계열 이슈를 더 깊게 보고 싶다면 다음 글도 함께 참고하면 좋습니다.
- EKS에서 gRPC DEADLINE_EXCEEDED 폭증 해결
- Go gRPC context deadline exceeded 9가지 원인
- K8s CrashLoopBackOff 원인별 로그·Probe 해결 가이드
0) 먼저: 502와 504를 ALB 관점에서 구분하기
ALB에서 흔히 보는 의미를 “디버깅 관점”으로 단순화하면 아래처럼 정리할 수 있습니다.
504(Gateway Timeout): ALB가 타겟으로부터 제시간에 응답을 못 받음502(Bad Gateway): ALB가 타겟과의 연결/응답을 정상적으로 해석 못 함 (연결 리셋, 잘못된 헤더, 조기 종료 등)
실무에서는 504가 “느림/정체/대기” 쪽, 502가 “중간에 끊김/프로토콜 불일치/리셋” 쪽으로 많이 나타납니다. 하지만 둘 다 결국 시간 초과나 연결 종료가 뿌리인 경우가 많습니다.
1) ALB Idle timeout (클라이언트-ALB, ALB-타겟)
가장 먼저 확인할 값입니다. ALB에는 idle_timeout이 있고, 기본값은 60초입니다(환경/시점에 따라 다를 수 있으니 콘솔에서 확인).
증상
- 특정 요청만 60초 근처에서 뚝 끊기며
502또는504가 증가 - 스트리밍/SSE/롱폴링/대용량 업로드에서 특히 자주 발생
확인 방법
- ALB 속성에서
idle_timeout확인 - CloudWatch 지표에서
HTTPCode_ELB_5XX_Count,TargetResponseTime,RequestCount의 동시 변화를 확인
해결 가이드
- 애플리케이션의 “최대 응답 시간”과 “업스트림 타임아웃”을 기준으로
idle_timeout을 조정 - 단, 무작정 크게 올리면 커넥션 점유가 길어져 리소스/동시성 문제를 악화시킬 수 있으니 서버 처리시간을 줄이거나 비동기화와 함께 접근
Terraform 예시(인라인 코드로 안전 처리):
resource "aws_lb" "app" {
name = "app-alb"
load_balancer_type = "application"
idle_timeout = 120
}
2) Target group health check 타임아웃과 임계치
헬스체크는 타임아웃 불일치의 “증폭기” 역할을 합니다. 실제 트래픽은 견디는데 헬스체크만 느려져서 타겟이 빠지면, 남은 타겟에 부하가 몰리며 502/504가 연쇄적으로 증가할 수 있습니다.
증상
- 특정 AZ/특정 타겟만 번갈아 OutOfService
- 배포 직후나 트래픽 급증 시점에 헬스체크 실패가 급증
확인 방법
- Target group의
Health check timeout,Interval,Healthy threshold,Unhealthy threshold확인 - 타겟 인스턴스/파드에서 헬스체크 엔드포인트 로그 확인
해결 가이드
- 헬스체크 엔드포인트는 DB/외부 API에 의존하지 않게 최소화
- 초기화가 느린 앱이라면 “부팅 완료 전에는 트래픽을 받지 않도록” readiness 개념을 분리
AWS CLI 예시:
aws elbv2 describe-target-groups \
--names my-tg \
--query 'TargetGroups[0].HealthCheckTimeoutSeconds'
3) ALB에서 타겟으로 가는 연결: keep-alive, 연결 재사용, 조기 종료
502가 많고, 특히 짧은 요청에서도 간헐적으로 발생한다면 “타겟이 연결을 먼저 닫는” 패턴을 의심해야 합니다. 대표적으로 앱 서버(또는 앞단 프록시)가 keep-alive 정책을 짧게 가져가거나, 커넥션을 재사용하는 동안 서버가 idle로 판단해 먼저 종료하는 경우입니다.
증상
502가 랜덤하게 튀고, 재시도하면 성공- 서버 로그에
connection reset by peer류가 보임
확인 방법
- 타겟(예: Nginx, Envoy, 앱 서버)의 keep-alive 관련 설정 확인
- VPC Flow Logs에서 RST 패턴이 보이는지 확인(가능한 경우)
해결 가이드
- ALB idle timeout과 서버 keep-alive idle timeout의 “관계”를 정리
- 일반적으로는 서버 쪽 keep-alive idle timeout을 ALB보다 약간 길게 두는 편이 안전(환경에 따라 다름)
Nginx 예시:
http {
keepalive_timeout 130s;
proxy_read_timeout 120s;
proxy_send_timeout 120s;
}
주의: 설정값 자체보다 서로의 우선 종료 주체가 누구인지가 핵심입니다.
4) 애플리케이션 서버(프레임워크/런타임) 요청 타임아웃
ALB는 기다리고 있는데 앱이 먼저 “요청 처리 한도 시간”을 넘겨 종료하면 502로 보일 수 있습니다(응답 헤더 전송 전에 종료, 워커 강제 종료 등).
증상
- 특정 API에서만 5xx가 집중
- 앱 로그에 타임아웃 예외가 명확히 기록됨
확인 방법
- Node.js/Express, Spring, Django, Go 서버 등에서 request timeout 설정 확인
- 워커 모델(예: Gunicorn, uWSGI, Node cluster)의 worker timeout 확인
해결 가이드
- “최대 처리시간”을 늘리기 전에, 왜 느린지(락, 외부 API, DB 슬로우쿼리)를 먼저 분해
- 불가피하게 오래 걸리는 작업은 비동기 잡 큐로 분리하고 API는
202로 즉시 응답
Node.js(예: http server) 예시:
import http from 'http';
const server = http.createServer(async (req, res) => {
// ...
res.end('ok');
});
server.requestTimeout = 120_000;
server.headersTimeout = 125_000;
server.listen(3000);
여기서도 포인트는 “값을 키우기”가 아니라 ALB, 프록시, 앱의 타임아웃을 일관되게 맞추는 것입니다.
5) 업스트림(예: Nginx/Envoy) proxy 타임아웃과 버퍼링
ALB 바로 뒤에 Nginx/Envoy가 있고, 그 뒤에 앱이 있는 구조라면 타임아웃은 최소 2단으로 늘어납니다. 이때 흔한 실수는 Nginx의 proxy_read_timeout은 길게 해놓고, send_timeout이나 client_body_timeout은 짧게 둬서 업로드/응답 전송 단계에서 끊기는 경우입니다.
증상
- 업로드 API에서
502/504증가 - 응답 바디가 큰 API에서 간헐적 실패
확인 방법
- Nginx access/error log에서 upstream timeout, client timeout 메시지 확인
- Envoy면 upstream request timeout, stream reset reason 확인
해결 가이드
- 업로드/다운로드/스트리밍 유형별로 타임아웃을 분리
- 버퍼링(디스크 스풀 포함)이 병목이면 버퍼 설정과 임시 디스크 용량도 함께 점검
Nginx 예시:
location /api/ {
proxy_connect_timeout 5s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
send_timeout 120s;
}
6) 데이터 계층 타임아웃(DB, Redis, 외부 API)과 풀 고갈
ALB에서 504가 늘 때 실제 원인은 앱이 DB 커넥션을 못 얻어 대기하거나, 외부 API가 느려서 워커가 묶이는 경우가 많습니다. 이때는 “타임아웃”이 아니라 “대기열”이 문제입니다.
증상
- 특정 시간대에만 급증(배치/크론/트래픽 피크)
- 앱 CPU는 낮은데 응답이 느림(대기)
확인 방법
- DB 슬로우쿼리, 커넥션 수, 락 대기 확인
- Redis latency, blocked clients 확인
- 외부 API 호출의 p95/p99와 재시도 폭증 확인
해결 가이드
- 커넥션 풀 크기 조정은 “증상 완화”일 뿐, 병목 쿼리/락/인덱스가 우선
- 외부 API는 타임아웃을 짧게, 재시도는 지수 백오프로 제한(동시 폭주 방지)
Go에서 HTTP 클라이언트 타임아웃 예시:
client := &http.Client{
Timeout: 3 * time.Second,
}
핵심은 “서버는 120초 기다리는데 외부 API는 30초 후 응답” 같은 불일치를 줄이고, 짧은 타임아웃 + 빠른 실패 + 제한된 재시도로 꼬리를 자르는 것입니다.
7) 오토스케일/배포 중 드레이닝과 연결 종료(등록 해제 지연)
ALB 타겟 그룹에는 등록 해제 시 연결을 얼마나 기다릴지(드레이닝)와 관련된 설정이 있습니다. 배포/스케일 인 시점에 연결이 중간에 끊기면 502가 튈 수 있습니다.
증상
- 배포 시점에만
502가 스파이크 - 스케일 인 직후부터 짧게 오류가 증가
확인 방법
- Target group의 deregistration delay(등록 해제 지연) 확인
- 컨테이너/인스턴스 종료 시그널 처리(예:
SIGTERM) 및 graceful shutdown 로그 확인
해결 가이드
- deregistration delay를 충분히 확보
- 앱이 종료 시그널을 받으면 즉시 새 요청은 거부하고, 진행 중 요청만 마무리하도록 구현
Kubernetes를 쓴다면 readiness probe와 terminationGracePeriodSeconds 조합도 함께 점검해야 합니다. 이 부분은 K8s CrashLoopBackOff 원인별 로그·Probe 해결 가이드의 probe/종료 섹션이 간접적으로 도움이 됩니다.
간단한 Node.js graceful shutdown 예시:
let shuttingDown = false;
process.on('SIGTERM', () => {
shuttingDown = true;
server.close(() => process.exit(0));
});
// 라우터에서 shuttingDown이면 503 반환 등으로 새 요청 차단
관측(Observability) 체크리스트: 어디서 끊기는지 10분 안에 좁히기
타임아웃은 “추측”이 아니라 “상관관계”로 좁혀야 합니다.
- ALB 액세스 로그를 켜고,
target_processing_time,elb_status_code,target_status_code를 함께 본다 - CloudWatch에서
TargetResponseTime의 p95/p99가 튀는지 확인한다 - 애플리케이션 로그에 request id(또는 trace id)를 넣고 ALB 로그와 조인한다
- 가능하면 분산 트레이싱으로 “대기 시간의 구간”(DNS, connect, TLS, TTFB, upstream)을 분해한다
ALB 액세스 로그 Athena 쿼리 예시(컬럼명은 테이블 정의에 맞게 조정):
SELECT
elb_status_code,
target_status_code,
approx_percentile(target_processing_time, 0.95) AS p95_target_time,
count(*) AS cnt
FROM alb_logs
WHERE from_iso8601_timestamp(time) > now() - interval '1' hour
GROUP BY 1, 2
ORDER BY cnt DESC;
실전 권장 순서(가장 빨리 효과 보는 점검 플로우)
- ALB
idle_timeout과 발생 시각(60초 근처인지)부터 확인 - 타겟 그룹 헬스체크 실패가 동반되는지 확인
- 배포/스케일 이벤트와 스파이크가 겹치는지 확인(드레이닝)
- 앱/프록시 로그에서 타임아웃 예외 또는 RST 패턴 확인
- DB/외부 API 지연 및 풀 고갈 지표 확인
- 각 계층 타임아웃 값을 “종단 기준”으로 재정렬
마무리: 타임아웃은 ‘값’이 아니라 ‘계약’이다
ALB 502/504 급증은 대개 “ALB가 나쁘다”가 아니라 클라이언트, ALB, 프록시, 앱, DB/외부 API까지 이어지는 타임아웃 계약이 깨진 결과입니다. 7곳을 순서대로 점검하면, 단순히 타임아웃을 늘리는 방식이 아니라 “어디서 먼저 끊는지”를 명확히 찾아 재발을 줄일 수 있습니다.
특히 gRPC를 쓰는 환경이라면 애플리케이션의 deadline과 프록시/로드밸런서의 타임아웃 불일치가 더 치명적으로 나타납니다. 이 경우는 EKS에서 gRPC DEADLINE_EXCEEDED 폭증 해결도 함께 보면서 deadline 전파와 재시도 정책까지 같이 정리하는 것을 권합니다.