- Published on
EKS에서 HTTP 431 해결 - 헤더·쿠키 과다 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡한데 특정 사용자/브라우저에서만 431 Request Header Fields Too Large가 터지는 경우가 있습니다. EKS 환경에서는 특히 **ALB Ingress(aws-load-balancer-controller) + Ingress Controller(NGINX) + 앱(Express/Spring/FastAPI 등)**처럼 홉이 여러 개라서, “어디에서” 헤더가 잘렸는지부터 헷갈리기 쉽습니다.
이 글은 **EKS에서 431이 발생하는 전형적인 패턴(쿠키 폭증, 프록시 체인, 보안 헤더 추가)**을 기준으로, 재현 → 관측 → 원인 분리 → 해결(단기/장기) 순서로 정리합니다.
> 참고: EKS에서 ALB 타임아웃 계열 이슈(408)와는 증상이 비슷해 보이지만 원인이 완전히 다릅니다. 타임아웃 문제는 아래 글이 더 적합합니다: EKS에서 ALB Ingress 408 Request Timeout 해결 가이드
HTTP 431이 의미하는 것
HTTP 431은 서버(또는 중간 프록시)가 요청 헤더가 너무 커서 처리할 수 없다고 판단할 때 반환합니다. 핵심은 “바디(payload)가 아니라 헤더”입니다.
대부분의 실무 케이스에서 헤더가 커지는 1순위는 Cookie 헤더입니다.
- 사용자가 여러 도메인/서브도메인을 오가며 쿠키가 누적됨
- SSO/세션/트래킹 쿠키가 커짐(JWT를 쿠키에 통째로 넣는 실수 포함)
- 프록시가
X-Forwarded-*,X-Amzn-Trace-Id같은 헤더를 추가하면서 누적 - 앱이
Set-Cookie를 과도하게 발급하여 다음 요청의Cookie가 폭증
EKS에서 431이 터지는 지점(체인별 분해)
EKS “웹 트래픽” 경로는 보통 다음 중 하나입니다.
ALB → Pod(앱) (Ingress 없이 Service type=NodePort/TargetGroupBinding 등)
ALB → Ingress Controller(NGINX) → Pod(앱)
CloudFront → ALB → (NGINX) → Pod
431은 이 중 어느 홉에서든 발생할 수 있습니다. 그래서 먼저 “응답을 누가 했는지”를 확인해야 합니다.
1) 응답 헤더로 발신자 추정
- ALB가 직접 4xx를 만들면, 종종
server: awselb/2.0같은 힌트가 보입니다(환경/설정에 따라 다를 수 있음). - NGINX Ingress라면
server: nginx또는 Ingress가 추가한 헤더가 보일 수 있습니다. - 앱이면 프레임워크 고유 헤더(예:
server: uvicorn,x-powered-by: Express)가 보일 수 있습니다.
아래처럼 curl -v로 확인합니다.
curl -vk https://example.com/some/path \
-H 'Cookie: a=...; b=...'
2) ALB 액세스 로그로 431 확인
ALB 액세스 로그를 S3로 켜두면, status_code=431이 찍힙니다. 중요한 건 target_status_code입니다.
elb_status_code=431이고target_status_code=-면 ALB에서 컷elb_status_code=431이고target_status_code=431이면 타겟(예: NGINX/앱)에서 431을 냈고 ALB가 전달
Athena로 빠르게 찾는 예시:
SELECT
time,
elb_status_code,
target_status_code,
request_url,
user_agent
FROM alb_logs
WHERE elb_status_code = 431
ORDER BY time DESC
LIMIT 50;
3) NGINX Ingress 로그에서 원인 좁히기
NGINX Ingress를 쓴다면, 431이 NGINX에서 발생하는지 확인하기 위해 컨트롤러 로그를 봅니다.
kubectl -n ingress-nginx logs deploy/ingress-nginx-controller \
--since=30m | egrep ' 431 |header|cookie|too large'
또는 access log 포맷을 커스터마이징해 $request_length, $http_cookie 길이 등을 찍어두면 재발 방지에 큰 도움이 됩니다.
가장 흔한 원인: 쿠키가 너무 큼(그리고 왜 EKS에서 더 자주 보이나)
쿠키는 브라우저가 도메인 단위로 들고 다니는 상태값이라, 다음 조건이 겹치면 폭증합니다.
Domain=.example.com으로 광범위하게 쿠키를 설정(모든 서브도메인에 전파)Path=/로 전 경로에 전파- 만료가 길고(또는 갱신이 잦고) 삭제 로직이 없음
- A/B 테스트, 트래킹 도구, SSO가 각각 쿠키를 추가
EKS에서 더 자주 보이는 이유는 Ingress/Service 레이어가 추가되며 헤더가 더 커지고, 환경에 따라 WAF/CloudFront/ALB/NGINX가 각자 제한을 갖기 때문입니다. 즉 “앱은 괜찮았는데 운영 인프라로 옮기니 터짐”이 흔합니다.
재현: 일부 사용자만 431인 케이스를 빠르게 잡는 방법
1) 브라우저에서 쿠키 크기 확인
크롬 DevTools → Application → Cookies에서 쿠키 개수/값을 확인합니다.
- 특히 값이 긴 쿠키(수 KB)를 찾습니다.
- JWT(eyJ...)가 쿠키에 들어가 있으면 거의 확정입니다.
2) 서버에서 요청 헤더 크기 측정(앱 레벨)
프록시를 통과하기 전에 이미 커졌는지 확인하려면, 앱에서 들어온 헤더의 대략적인 크기를 로그로 남깁니다(임시로라도).
Node.js(Express) 예시:
app.use((req, res, next) => {
const raw = JSON.stringify(req.headers);
const bytes = Buffer.byteLength(raw, 'utf8');
console.log('header_bytes=', bytes, 'cookie_bytes=', (req.headers.cookie || '').length);
next();
});
Python(FastAPI/Starlette) 예시:
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def log_header_size(request: Request, call_next):
headers = dict(request.headers)
header_bytes = sum(len(k) + len(v) for k, v in headers.items())
cookie_bytes = len(headers.get("cookie", ""))
print({"header_bytes": header_bytes, "cookie_bytes": cookie_bytes})
return await call_next(request)
> 주의: 개인정보/토큰이 포함될 수 있으니 값 자체를 로그로 남기지 말고 길이만 남기는 게 안전합니다.
해결 전략: “제한을 늘리기”보다 “헤더를 줄이기”가 정답
431을 해결하는 방법은 크게 두 갈래입니다.
- 인프라/프록시 제한을 늘려서 통과시키기(단기 처방)
- 쿠키/헤더 설계를 바꿔 크기를 줄이기(근본 해결)
운영 안정성을 위해서는 2)가 필수입니다. 1)만 하면 언젠가 다시 터집니다.
Ingress(NGINX)에서의 단기 처방(제한 상향)
NGINX Ingress를 사용한다면, 헤더 버퍼 관련 설정으로 431/400을 완화할 수 있습니다. 대표적으로:
large_client_header_buffersclient_header_buffer_size
Ingress annotation 또는 ConfigMap으로 조정합니다(컨트롤러 종류에 따라 키가 다를 수 있음).
예시(컨트롤러 ConfigMap 방식, 환경에 맞게 조정):
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
large-client-header-buffers: "4 16k"
client-header-buffer-size: "16k"
적용 후 컨트롤러 롤링:
kubectl -n ingress-nginx rollout restart deploy/ingress-nginx-controller
> 포인트: 이건 “쿠키 폭탄”을 허용하는 방향이라 메모리 사용량이 늘 수 있고, 악성 트래픽에 취약해질 수 있습니다. 반드시 근본 원인(쿠키 정리)과 병행하세요.
ALB에서의 현실적인 제약과 우회
ALB는 요청 헤더 크기에 대한 제한이 있고(공식 문서 기준으로도 상한이 존재), 이를 무제한으로 늘릴 수는 없습니다. 따라서 ALB에서 431이 나면 NGINX 설정을 올려도 소용이 없는 경우가 많습니다.
이때 선택지는 사실상 두 가지입니다.
- 헤더/쿠키를 줄인다(권장)
- 아키텍처를 바꾼다(예: CloudFront에서 쿠키를 줄이거나, 경로/도메인 분리로 쿠키 전파 범위를 줄임)
근본 해결 1: 쿠키 설계 바로잡기(도메인/패스/수명)
쿠키가 커지는 프로젝트는 보통 “처음엔 편해서” 아래처럼 설정합니다.
Domain=.example.comPath=/- 여러 앱이 같은 이름의 쿠키를 각자 갱신
개선 체크리스트:
- 쿠키
Domain을 가능한 좁히기(서브도메인 전체 공유가 꼭 필요한지 재검토) - 쿠키
Path를 필요한 경로로 제한 - 만료/갱신 정책 정리(불필요한 장기 쿠키 제거)
- 환경별(스테이징/프로덕션) 쿠키 네임스페이스 분리
예: 인증 쿠키를 auth.example.com에만 두고, 서비스 도메인에는 최소한의 식별자만 둡니다.
근본 해결 2: “JWT를 쿠키에 통째로” 넣지 말기
431을 만드는 최악의 패턴 중 하나는 긴 JWT(또는 여러 토큰)를 쿠키로 저장하는 것입니다.
대안:
- 서버 세션 + 짧은 세션 ID 쿠키(권장)
- 토큰은 Authorization 헤더로 보내되, 길이를 통제(불필요한 클레임 제거)
- 리프레시 토큰/액세스 토큰을 둘 다 쿠키로 두는 설계라면 재검토
서버 세션 예시(개념):
- Cookie:
sid=abc123(수십 바이트) - 세션 데이터는 Redis/DynamoDB 등에 저장
근본 해결 3: Set-Cookie 폭주(중복 발급) 버그 잡기
간헐적 431은 “특정 조건에서만” Set-Cookie가 여러 개 쌓이는 버그일 때가 많습니다.
- 로그인 리다이렉트 체인에서 매번 신규 쿠키 추가
Path가 다른 동일 이름 쿠키가 중복- 로그아웃 시 만료 쿠키를 제대로 안 내려서 “유령 쿠키”가 남음
대응:
- 로그인/갱신/로그아웃 플로우에서
Set-Cookie개수와 이름을 테스트로 고정 - e2e 테스트에서 쿠키 개수 상한을 검증
Playwright로 쿠키 개수/크기 감시 예시:
import { test, expect } from '@playwright/test';
test('cookie budget', async ({ page, context }) => {
await page.goto('https://example.com');
// 로그인 등 플로우 수행
const cookies = await context.cookies();
const total = cookies.reduce((sum, c) => sum + (c.value?.length ?? 0), 0);
expect(cookies.length).toBeLessThan(30);
expect(total).toBeLessThan(3000);
});
관측/운영: “쿠키 예산(cookie budget)”을 지표로 만들기
431은 터지고 나서야 알면 늦습니다. 운영에서는 아래 지표를 추천합니다.
request_header_bytes(가능하면 ALB/NGINX/앱 각각)cookie_bytes- 4xx 중 431 비율
NGINX Ingress에서 로그 포맷을 확장할 수 있다면:
$request_length(요청 라인+헤더 포함 길이)$http_cookie길이는 직접 length를 내기 어렵지만, Lua/모듈 또는 앱에서 보완
앱에서라도 cookie_bytes를 샘플링 로깅해두면 “어느 릴리즈 이후 증가했는지” 추적이 가능합니다.
자주 헷갈리는 포인트
431 vs 413
- 431: 헤더가 큼
- 413: 바디(payload)가 큼
업로드 413 대응은 별도 전략이 필요합니다: OpenAI Responses API 413 에러 업로드 용량 제한과 청크 전략
431이 400으로 보이기도 함
일부 프록시는 헤더 과다를 431 대신 400으로 뭉개서 반환합니다. 그래서 “400이 늘었는데 원인을 모르겠다”면, 쿠키/헤더 크기부터 의심하세요.
실전 처방 순서(추천)
- ALB 액세스 로그로 431의 발신 지점(ALB vs 타겟)부터 확정
- 사용자 브라우저에서 Cookie 크기/개수 확인(특히 JWT/SSO)
- 앱 또는 NGINX에서 header_bytes/cookie_bytes를 길이로 로깅(값은 금지)
- 단기: NGINX라면 버퍼 상향으로 급한 불 끄기
- 장기: 쿠키 스코프/수명 정리 + 서버 세션/토큰 설계 개선 + 중복
Set-Cookie제거
마무리
EKS에서 HTTP 431은 “인프라가 불안정해서”가 아니라, 대부분 상태(쿠키/헤더)를 클라이언트에 너무 많이 실어 나른 결과입니다. 특히 ALB 앞단에서 막히면 설정으로 뚫기 어렵기 때문에, 결국은 쿠키 예산을 정하고 설계를 줄이는 방향이 가장 확실합니다.
운영 중 비슷하게 “요청은 왔는데 중간에서 끊기는” 증상이라면, 타임아웃 계열(408/504)과 혼동하지 말고 먼저 헤더/쿠키 크기부터 계측해 보세요. 431은 계측만 제대로 해도 원인까지 가는 시간이 크게 줄어듭니다.