- Published on
AWS ALB 504 Gateway Timeout 원인·해결 12가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 살아 있는데도 ALB에서만 504 Gateway Timeout이 터지면, 대부분은 "ALB가 타깃(Target)으로부터 제한 시간 내에 유효한 응답을 못 받았다"는 뜻입니다. 중요한 포인트는 504가 클라이언트-ALB 구간 문제가 아니라, 대개 ALB-타깃 구간(또는 타깃 내부 처리 지연)에서 발생한다는 점입니다.
이 글은 운영에서 자주 맞닥뜨리는 ALB 504 원인을 12개로 쪼개고, 각각을 증상, 확인 방법, 해결책 중심으로 정리합니다. (ECS, EC2, EKS 모두에 적용됩니다.)
0) 먼저 확인할 것: 504의 "주체"가 ALB 맞나?
동일하게 504라도 ALB가 만든 504인지, 애플리케이션이 만든 504인지에 따라 접근이 달라집니다.
- ALB가 만든 경우: ALB 액세스 로그에
elb_status_code=504로 남는 경향이 큽니다. - 앱이 만든 경우:
target_status_code=504가 찍히거나(로그 포맷에 따라), 앱 로그에 504 응답이 남습니다.
ALB 액세스 로그를 켜고(가능하면), 아래 필드를 같이 보세요.
elb_status_codetarget_status_coderequest_processing_time,target_processing_time,response_processing_time
1) ALB Idle timeout(기본 60초) 초과
가장 흔한 원인입니다. ALB는 연결이 일정 시간 동안 유휴 상태면 끊습니다. 특히 다음 상황에서 잘 터집니다.
- 대용량 응답 생성, 서버 사이드 렌더링, PDF/리포트 생성
- 업스트림(DB, 외부 API) 대기 시간이 길어짐
- 스트리밍처럼 보이지만 실제로는 버퍼링으로 인해 데이터가 늦게 나감
확인
- ALB 액세스 로그에서
target_processing_time이60근처로 치솟거나, 특정 임계값에서 반복 - 클라이언트는 정확히 60초(또는 설정값) 전후로 끊김
해결
- ALB의
idle_timeout을 늘립니다. 다만 무작정 크게 잡기보다, 서버/업스트림 지연을 줄이는 개선과 함께 가야 합니다.
aws elbv2 modify-load-balancer-attributes \
--load-balancer-arn `ALB_ARN` \
--attributes Key=idle_timeout.timeout_seconds,Value=120
2) 타깃 응답 헤더 지연(첫 바이트가 늦음)
ALB는 타깃이 연결되었다고 끝이 아니라, 유효한 HTTP 응답을 제때 받아야 합니다. 앱이 바디를 늦게 보내는 것보다 더 치명적인 건 헤더(첫 바이트) 자체가 늦게 나오는 경우입니다.
확인
- 앱 APM에서 TTFB(Time To First Byte)가 급증
- ALB 로그의
target_processing_time이 길고, 앱 로그에는 요청이 늦게 찍히거나(핸들러 진입이 늦음) 처리 시작이 지연
해결
- 요청 초기에 헤더를 빠르게 내려주도록(가능하면) 처리 구조 개선
- 작업성 요청은 비동기(큐)로 전환하고 즉시
202반환 - 느린 쿼리/락/외부 API를 제거하거나 타임아웃을 짧게
3) 타깃(EC2/ECS/EKS)에서 커넥션 수/스레드 고갈
ALB는 타깃으로 연결을 시도하지만, 타깃이 더 이상 소켓/스레드를 못 받아주면 연결이 지연되고 결국 504로 이어질 수 있습니다.
대표 증상
- 피크에만 504 폭증
- 타깃 CPU는 낮은데도 지연이 큼(스레드 풀 잠김, 커넥션 풀 고갈)
확인
- 애플리케이션의 워커 수, 스레드 풀, 커넥션 풀 상태
- OS 레벨에서
SYN_RECV,TIME_WAIT증가
# EC2/노드에서
ss -s
ss -ant state time-wait | wc -l
ulimit -n
해결
- 웹 서버/런타임 워커 증설(예: gunicorn workers, Node.js cluster, JVM thread pool)
- DB/HTTP 클라이언트 커넥션 풀 상향 및 누수 점검
- Keep-Alive/재사용 설정 최적화
4) 헬스체크는 통과하지만 실제 트래픽 경로가 다름
헬스체크는 보통 GET /health 같은 가벼운 엔드포인트입니다. 그런데 실제 요청은 DB, 외부 API, 파일 시스템 등 더 많은 의존성을 타며 느려질 수 있습니다.
확인
- 헬스체크는
healthy인데 특정 API만 504 /health는 빠른데/api/report같은 엔드포인트만 느림
해결
- 헬스체크를 "진짜 서비스 가능"을 반영하도록 개선(의존성 최소 1개는 확인)
- 느린 엔드포인트는 별도 워커/큐로 분리
- 읽기 전용 캐시 도입
5) 보안 그룹/NACL/라우팅 이슈로 타깃에 도달 불가(간헐적)
ALB에서 타깃으로의 통신은 보안 그룹, NACL, 라우팅에 영향을 받습니다. 특히 멀티 AZ에서 특정 AZ만 문제일 때 504가 간헐적으로 발생합니다.
확인
- 특정 AZ의 타깃에서만 504
- 타깃 그룹에서 특정 인스턴스만 응답이 느림
해결
- ALB 보안 그룹에서 타깃 포트로 아웃바운드 허용(보안 그룹 참조 포함)
- 타깃 보안 그룹에서 ALB 보안 그룹 인바운드 허용
- NACL에서 ephemeral 포트 범위 허용(상태 비저장이라 왕복 모두 필요)
6) EKS에서 Service Endpoints가 0 또는 잘못된 포트 매핑
EKS에서 ALB Ingress Controller(또는 AWS Load Balancer Controller)를 쓰는 경우, ALB는 결국 Kubernetes Service의 엔드포인트로 라우팅됩니다. 이때 엔드포인트가 비어 있거나 포트가 어긋나면 504/5xx가 발생합니다.
- 관련해서는 이 글도 같이 보면 원인 좁히는 데 도움이 됩니다: EKS에서 Pod는 뜨는데 Service Endpoints가 0일 때
확인
kubectl get svc -n `NAMESPACE`
kubectl get endpoints -n `NAMESPACE` `SERVICE_NAME` -o yaml
kubectl describe ingress -n `NAMESPACE` `INGRESS_NAME`
해결
- Service selector가 Pod label과 일치하는지 확인
- Service
targetPort가 컨테이너 포트와 일치하는지 확인 - readinessProbe 실패로 엔드포인트에서 제외되는지 확인
7) 타깃의 readiness/liveness 설정 문제로 트래픽이 "준비 안 된" Pod로 유입
Kubernetes에서는 readinessProbe가 제대로 동작하지 않거나, 준비되기 전에 트래픽을 받는 구조면 초기 요청이 타임아웃으로 이어질 수 있습니다.
확인
- 배포 직후에만 504
- Pod 이벤트에 readiness 실패가 반복
kubectl describe pod -n `NAMESPACE` `POD_NAME`
kubectl get events -n `NAMESPACE` --sort-by=.lastTimestamp | tail -n 50
해결
- readinessProbe를 실제 의존성 준비 이후에
success가 되도록 조정 - 초기화가 긴 앱은 startupProbe 도입
- 롤링 업데이트 시
maxUnavailable,preStop,terminationGracePeriodSeconds튜닝
8) 애플리케이션/업스트림 타임아웃이 ALB보다 길어 "끝까지 기다리다" 504
예를 들어 앱의 HTTP 클라이언트 타임아웃이 120초인데 ALB idle timeout이 60초면, 앱은 계속 기다리다가 ALB가 먼저 끊어 504가 납니다.
확인
- 앱 로그에는 외부 API 호출 대기 중으로 남고, ALB는 60초 즈음 504
해결
- 타임아웃 계층을 일관되게 설계합니다.
- 예: 클라이언트(브라우저)
75s/ ALB70s/ 앱 서버65s/ 외부 API60s
- 예: 클라이언트(브라우저)
- 재시도는 지연을 더 키울 수 있으니, 서킷 브레이커/백오프를 적용
9) HTTP/2, gRPC, WebSocket에서의 프록시 타임아웃/keep-alive 미스매치
ALB는 HTTP/2, WebSocket을 지원하지만, 서버의 keep-alive 정책과 맞지 않으면 장시간 연결에서 타임아웃이 발생할 수 있습니다.
확인
- 장시간 유지 연결에서만 504
- 특정 클라이언트(모바일/프록시)에서만 재현
해결
- 서버의 keep-alive timeout을 ALB idle timeout보다 약간 짧게 또는 정책적으로 일치
- WebSocket은 NLB가 더 적합한 경우도 검토
10) 타깃 등록/해제(스케일 인/배포) 타이밍 문제
오토스케일링이나 롤링 배포 중에 연결이 끊기면 504가 튈 수 있습니다. 특히 종료되는 인스턴스/Pod가 아직 트래픽을 받는 상태에서 프로세스가 먼저 내려가면 문제가 납니다.
확인
- 배포/스케일 이벤트 직후 504 증가
- 타깃 그룹에서
draining상태가 짧거나 무시됨
해결
- 연결 드레이닝(등록 해제 지연) 설정 확인
- 애플리케이션에
SIGTERM처리 및 graceful shutdown 구현 - Kubernetes는
preStop훅과 충분한terminationGracePeriodSeconds설정
11) DNS/서비스 디스커버리/내부 프록시로 인한 지연 전파
ALB 뒤의 앱이 다시 내부 프록시(Envoy, Nginx)나 서비스 디스커버리를 타고, 그 내부에서 DNS 지연이나 재시도 폭증이 발생하면 최종적으로 ALB 504로 보입니다.
확인
- 앱 자체 로직은 단순한데 내부 호출이 길어짐
- DNS 쿼리 지연, 재시도 폭증
해결
- DNS TTL/캐시 정책 점검
- 내부 프록시의 재시도 정책(특히 무제한 재시도) 제거
- 호출 체인에 전체 타임아웃 예산을 부여
12) 관측(로그/메트릭/트레이싱) 부재로 원인 파악이 늦어짐
504는 "결과"라서, 원인을 빠르게 찾으려면 관측이 필수입니다. 아래 3가지만 갖춰도 대부분의 504는 30분 내로 좁혀집니다.
필수 관측 3종 세트
- ALB 액세스 로그(요청별 처리 시간 3종)
- 타깃(앱) 로그에서 요청 ID 상관관계
- APM 또는 분산 트레이싱(업스트림 호출 시간)
CloudWatch 지표도 함께 보세요.
HTTPCode_ELB_5XX_CountTargetResponseTimeHealthyHostCount
예: CloudWatch에서 최근 1시간 504 추적(개념 예시)
# 실제 조회는 aws cloudwatch get-metric-statistics 또는 콘솔을 사용
# 여기서는 어떤 지표를 봐야 하는지 "이름"을 고정해 두는 용도
echo "Check metrics: HTTPCode_ELB_5XX_Count, TargetResponseTime, HealthyHostCount"
관측을 강화하면서, 애플리케이션 레벨에서도 "왜 느린지"를 즉시 알 수 있게 해두면 좋습니다. 예를 들어 DB 쿼리 로그, 외부 API 타임아웃 로그, 큐 대기 시간 등을 남기면 504를 단순 네트워크 문제로 오인하는 시간을 줄일 수 있습니다.
빠른 진단 순서(현장용)
- ALB 액세스 로그에서
elb_status_code와 처리 시간 3종 확인 idle_timeout(기본 60초)과 실제 끊기는 시간이 일치하는지 확인- 타깃 그룹
HealthyHostCount변동, 특정 AZ 편향 확인 - 타깃(노드/Pod)에서 커넥션/스레드/CPU/메모리/GC 확인
- 업스트림(DB/외부 API) 타임아웃과 재시도 정책 확인
- EKS라면 Service Endpoints, readinessProbe부터 확인
EKS 환경에서 엔드포인트가 비거나 readiness로 트래픽이 흔들리는 케이스가 특히 흔하니, 필요하면 위에서 언급한 글(EKS에서 Pod는 뜨는데 Service Endpoints가 0일 때)을 함께 체크하면 진단 시간을 크게 줄일 수 있습니다.
마무리
ALB의 504는 "ALB가 나쁘다"기보다, 대부분 타깃이 제시간에 응답을 못 하는 구조(타임아웃 불일치, 리소스 고갈, 업스트림 지연, 엔드포인트/헬스체크 불일치)에서 나옵니다.
위 12가지를 체크리스트처럼 순서대로 밟으면, 원인을 네트워크로 몰아가기 전에 **시간 예산(Timeout budget)**과 병목 지점을 먼저 특정할 수 있고, 재발 방지까지 설계할 수 있습니다.