| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- rewrites
- getModifierState
- styled components
- SCSS
- react
- gitaction
- dart 변수
- typescript react
- nextjs
- API 토큰
- next.js css
- There isn’t anything to compare
- bootstrap
- github io
- API token
- CSS
- react env
- nextjs .env
- npm styled-reset
- next.js
- github
- ngrok설치
- icon
- react typescript
- input type=file
- createGlobalStyle
- Git
- git lab
- fetch
- ngrok실행
- Today
- Total
꾸준히 성장하는 개발자
Next.js + NextAuth | 400 에러 — Cookie Too Large 본문
운영 중이던 서비스에서 어느 순간부터 모든 사용자에게 동일한 에러가 발생하기 시작했다.
특이한 점은 새로 배포한 코드가 전혀 없었다는 것이다.
로그인은 정상적으로 되는데, 보호된 페이지로 이동하는 순간 400 Bad Request 가 발생했다. 이번 글은 그 원인을 추적하고 해결한 과정을 정리한 내용이다.
사용 스택
- Next.js 14 (App Router)
- NextAuth (JWT 전략)
- AWS EC2 + PM2 + nginx
- 외부 포털 SSO
- iframe + postMessage 로 토큰 전달
1. 증상
현상은 단순했다.
- 로그인 성공
- 보호된 페이지 진입 시 즉시 400 Bad Request
- nginx 기본 에러 페이지 출력
에러 메시지는 다음과 같았다.
400 Bad Request
Request Header Or Cookie Too Large
메시지가 매우 친절했다.
HTTP 요청 헤더, 정확히는 쿠키 크기가 서버 허용치를 초과한 상황이었다.
2. 원인 분석 — NextAuth JWT 가 비대해진 이유
브라우저 DevTools → Application → Cookies 를 열어보니 바로 이상한 점이 보였다.
| __Host-app.csrf-token | 152 B |
| __Secure-app.callback-url | 65 B |
| __Secure-app.session-token.0 | 3,961 B |
| __Secure-app.session-token.1 | 3,961 B |
| __Secure-app.session-token.2 | 195 B |
| appToken | 315 B |
| 합계 | 약 8.6 KB |
세션 토큰이 .0, .1, .2 로 나뉘어 있었다.
이건 NextAuth 의 쿠키 청크 분할(chunking) 동작이다.
NextAuth 의 쿠키 청크 분할
브라우저 쿠키는 일반적으로 개당 약 4KB 제한이 있다.
NextAuth 는 JWT 가 커지면 자동으로 쿠키를 여러 개로 분할한다.
예를 들면:
__Secure-app.session-token.0
__Secure-app.session-token.1
__Secure-app.session-token.2
브라우저는 이걸 정상 저장한다.
문제는 HTTP 요청 시점이다.
결국 모든 청크가 하나의 Cookie: 헤더로 합쳐져 서버로 전송된다.
즉:
Cookie: token.0=...; token.1=...; token.2=...
결과적으로 헤더 전체 크기는 그대로 커진다.
JWT 안에 뭐가 들어있었나
원인은 authorize() 반환값에 있었다.
return {
...userInfo,
token,
currentItem,
itemList, // ← 선택 가능한 전체 목록
};
문제는 itemList 였다.
사용자가 접근 가능한 전체 목록 데이터를 JWT 안에 그대로 넣고 있었다.
사용자 데이터가 늘어날수록 JWT 크기도 계속 커졌고, 결국 쿠키 전체가 약 8KB 수준까지 증가한 상태였다.
그리고 이 데이터는 모든 HTTP 요청마다 계속 따라다녔다.
3. 어디서 막히는지 추적하기
우선 어느 구간에서 요청이 차단되는지부터 확인해야 했다.
구조는 다음과 같았다.
브라우저
↓
ALB / CloudFront
↓
nginx
↓
Next.js(Node.js)
각 계층의 기본 헤더 제한은 대략 이렇다.
| Node.js (--max-http-header-size) | 16 KB | 가능 |
| nginx (large_client_header_buffers) | 8 KB | 가능 |
| AWS ALB | 16 KB | 불가능 |
| CloudFront | 32 KB | 불가능 |
어디서 막혔는지 확인하는 방법
DevTools → Network 탭에서 응답 헤더를 보면 된다.
Server: nginx
즉 nginx 단계에서 차단된 것이었다.
다행히 nginx 는 설정 변경으로 임시 대응이 가능했다.
4. 임시 핫픽스 — 헤더 제한 증설
4-1. Node.js 헤더 한계 늘리기
package.json
{
"scripts": {
"dev": "NODE_OPTIONS='--max-http-header-size=65536' next dev --experimental-https",
"start": "NODE_OPTIONS='--max-http-header-size=65536' next start -p $PORT"
}
}
PM2 사용 시에는 ecosystem.config.js 에도 추가하는 것이 안전하다.
{
name: 'my-app',
script: 'pnpm start',
env_prod: {
PORT: 3000,
NODE_OPTIONS: '--max-http-header-size=65536',
},
}
⚠️ PM2 환경변수 함정
여기서 중요한 포인트가 하나 있다.
pm2 restart 만 하면 환경변수가 갱신되지 않는다.
반드시 --update-env 옵션을 붙여야 한다.
pm2 restart my-app --update-env --env prod
이거 빼먹으면 진짜 한참 헤맨다.
4-2. nginx 헤더 버퍼 증설
운영 nginx 설정 수정:
server {
listen 80;
server_name app.example.com;
# 큰 쿠키 헤더 허용
client_header_buffer_size 16k;
large_client_header_buffers 8 32k;
location / {
proxy_pass http://127.0.0.1:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 큰 Set-Cookie 대응
proxy_buffer_size 32k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;
}
}
nginx 설정 의미 정리
| client_header_buffer_size 16k | 클라이언트 요청 헤더 1차 버퍼 |
| large_client_header_buffers 8 32k | 큰 헤더 처리용 버퍼 |
| proxy_buffer_size 32k | 업스트림 응답 헤더 버퍼 |
| proxy_buffers 8 32k | 업스트림 응답 본문 버퍼 |
| proxy_busy_buffers_size 64k | 응답 전송 중 사용 가능한 버퍼 |
적용 전 반드시 문법 검사
sudo nginx -t
sudo nginx -s reload
nginx -t 실패했는데 reload 하면 운영 장애 난다.
무조건 먼저 검사하는 습관이 필요하다.
결과
핫픽스 적용 후:
- 운영 환경 400 에러 즉시 해소
- Cookie 헤더 약 10KB
- nginx 제한 32KB
즉 현재 약 31% 수준 사용 중이었다.
5. 그런데 이 핫픽스, 얼마나 버틸까?
문제는 구조 자체였다.
쿠키 크기는 사용자 데이터 증가에 따라 계속 커질 수밖에 없다.
현재 상태:
헤더 한계: 32KB
현재 사용량: 약 10KB
남은 여유: 약 22KB
항목 하나가 평균 150B 라고 가정하면 현재의 약 3배 정도까지는 버틴다.
하지만:
- 권한 정보 증가
- 메뉴 배열 증가
- 추가 메타데이터 누적
같은 상황이 생기면 금방 다시 터질 수 있다.
즉 이건 임시 처치일 뿐이다.
6. 근본 해결 — JWT 에서 무거운 데이터 제거
진짜 해결은 단순하다.
JWT 를 다시 “인증 정보” 역할로 되돌리는 것이다.
itemList 는 인증 데이터가 아니다.
그냥 UI 렌더링용 데이터다.
따라서 API fetching 영역으로 분리해야 한다.
Before — JWT 가 모든 걸 들고 있는 구조
return {
userId,
type,
role,
token,
currentItem,
itemList,
};
After — JWT 최소화 + React Query 로 분리
authOptions.ts
return {
userId,
type,
role,
token,
currentItem,
};
api/items.ts
'use server';
export async function getItemList() {
const token = cookies().get('appToken')?.value || '';
const res = await fetch(`${API_BASE}/items`, {
headers: {
Authorization: token,
},
cache: 'no-store',
});
const json = await res.json();
return json.result?.list ?? [];
}
hooks/useItemList.ts
export const useItemList = () =>
useQuery({
queryKey: ['itemList'],
queryFn: getItemList,
staleTime: 1000 * 60 * 30,
});
결과
변경 후:
JWT 크기
8KB → 1KB 미만
항목 수가 수천 개가 되어도 쿠키는 거의 증가하지 않는다.
그리고 모든 요청마다 불필요한 8KB 헤더가 따라다니는 문제도 사라진다.
7. 이번 사건에서 배운 점
1) JWT 는 인증 정보만 담아라
JWT 는 매 요청마다 따라다닌다.
그래서 “사용자 정보니까 다 넣자” 는 접근은 위험하다.
특히:
- 권한 목록
- 메뉴 트리
- 선택 가능한 리스트
- UI 렌더링 데이터
이런 건 JWT 가 아니라 API 로 분리해야 한다.
2) NextAuth 청크 분할은 해결책이 아니다
청크 분할은 단지 브라우저 저장 제한을 우회하는 기능이다.
요청 시에는 결국 전부 합쳐진다.
즉:
청크 쿠키가 보이기 시작했다
= 이미 위험 신호다
3) 헤더 제한은 다층 구조다
브라우저
↓
CloudFront (32KB)
↓
ALB (16KB)
↓
nginx (8KB)
↓
Node.js (16KB)
실제 한계는 가장 작은 제한값이다.
특히 ALB 는 제한 변경이 불가능하다.
4) PM2 의 --update-env 는 거의 필수
pm2 restart <app> --update-env --env prod
PM2 환경변수 안 먹는 문제의 대부분은 여기서 발생한다.
마무리
이번 장애는 코드 변경이 없어도 사용자 데이터가 누적되면 운영이 무너질 수 있다는 걸 보여준 사례였다.
JWT 에 데이터를 추가할 때마다 앞으로는 반드시 이 질문을 하게 될 것 같다.
"이 데이터가 매 요청마다 항상 따라다녀도 괜찮은가?"
핫픽스로 급한 불은 껐지만, 결국 근본 해결은 JWT 를 가볍게 유지하는 것이다.
같은 문제를 겪는 누군가에게 도움이 되길 바란다.
'Next.js' 카테고리의 다른 글
| [PWA]Next.js 프로젝트에서 PWA 설정하기 (0) | 2026.01.29 |
|---|---|
| [Next.js] Pre-rendering / getServerSideProps (0) | 2022.08.17 |
| [Next.js] api키 숨기기 / redirect / rewrite (0) | 2022.08.16 |
| [Next.js] _app.js / 커스텀 App component (0) | 2022.08.04 |
| [Next.js] Next.js 시작하기 (0) | 2022.05.31 |
