1. 개요
백엔드 WAS 앞에 Nginx를 리버스 프록시로 두는 구성은 흔하다. 평소 잘 돌던 경로가 어느 날 특정 사용자, 특정 API에서만 502를 내뱉을 때가 있다. 로그인 직후나 세션이 커진 계정에서만 재현되는 식이다. 나도 이 증상을 처음 만났을 때는 백엔드가 죽은 줄 알고 애플리케이션 로그부터 뒤졌는데, 정작 원인은 Nginx가 응답 헤더를 담을 버퍼가 모자랐던 것이었다.
결론부터 말하자면, 이 502는 업스트림이 보낸 응답 헤더가 proxy_buffer_size 한 장에 안 들어가서 생긴다. 해결은 proxy_buffer_size를 키우는 것이지만, 이 값만 단독으로 올리면 proxy_busy_buffers_size 자동 계산값이 상한을 넘어 nginx -t가 거부한다. 그래서 proxy_buffer_size, proxy_buffers, proxy_busy_buffers_size 세 지시어를 함께 맞춰야 한다.
대상 버전은 Nginx 1.24 이상(1.26/1.27 포함)이며, 아래 기본값과 계산 규칙은 공식 ngx_http_proxy_module 문서 기준이다. 값은 플랫폼 메모리 페이지 크기에 따라 다를 수 있는데, 이 글은 가장 흔한 x86-64 리눅스(페이지 4KB)를 기준으로 적는다.
2. 증상: 502와 error.log 한 줄
클라이언트는 그냥 502 Bad Gateway를 받는다. 원인은 error.log에 정확히 찍힌다.
2026/09/07 13:22:10 [error] 1123#1123: *45 upstream sent too big header while reading response header from upstream, client: 203.0.113.10, server: sarc.io, request: "GET /api/me HTTP/1.1", upstream: "http://127.0.0.1:3000/api/me", host: "sarc.io"
핵심 단서는 reading response header from upstream이다. 응답 본문이 아니라 응답 헤더가 문제라는 뜻이다. Nginx는 업스트림에서 온 상태줄과 헤더 전체를 첫 버퍼 한 장에 읽어들인다. 이 첫 버퍼의 크기가 proxy_buffer_size다. 헤더 덩어리가 그 한 장을 넘으면 Nginx는 더 읽지 못하고 502로 끊는다.
어떤 응답이 헤더를 키우는지 감을 잡으려면 백엔드 응답 헤더를 직접 재보면 된다. Nginx를 거치지 않고 업스트림에 바로 붙어 확인한다.
$ curl -s -D - -o /dev/null http://127.0.0.1:3000/api/me \
-H 'Cookie: session=...(실제 세션)...' | wc -c
9241
여기서 나오는 헤더 바이트가 기본 proxy_buffer_size(4KB)를 넘으면 502가 난다. 범인은 대개 큼직한 Set-Cookie, JWT를 통째로 쿠키에 담는 구성, 여러 개로 쪼개진 세션 쿠키, 길게 붙는 WWW-Authenticate 같은 헤더다.
2-1. 헤더 버퍼란 무엇인가
Nginx가 업스트림 응답을 버퍼링할 때(proxy_buffering on, 기본값), 응답은 두 부분으로 나뉘어 처리된다. 하나는 상태줄과 헤더 블록이고, 다른 하나는 본문이다. 상태줄과 헤더는 반드시 버퍼 한 장에 통째로 들어가야 한다. 여러 장에 걸쳐 이어 담지 않는다. 그 한 장의 크기가 proxy_buffer_size이고, 이 글의 502는 바로 이 한 장이 모자랄 때 나는 에러다.
참고로 이건 클라이언트가 보낸 요청 헤더를 담는 large_client_header_buffers와는 다른 지시어다. 큰 쿠키는 요청 방향과 응답 방향 양쪽을 다 건드릴 수 있는데, 요청 헤더가 너무 크면 Nginx가 400(또는 431)을 내고, 업스트림이 내려주는 응답 헤더가 크면 지금 다루는 502가 난다. 이 글은 응답 방향, 즉 proxy_buffer_size 쪽이다.
3. 세 지시어와 기본값
응답 헤더 버퍼링에 관여하는 지시어는 세 개다. 하나씩 한 줄 정의로 끊어 본다.
proxy_buffer_size - 업스트림 응답의 첫 부분(상태줄 + 헤더)을 읽는 버퍼 한 장의 크기. 502를 직접 좌우하는 값이다.
proxy_buffers - 응답 본문을 버퍼링하는 버퍼의 개수와 각 장의 크기.
proxy_busy_buffers_size - 아직 응답을 다 읽지 못한 상태에서, 이미 클라이언트로 내보내는 데 쓰이는 버퍼의 총량 상한.
x86-64 리눅스에서의 기본값은 다음과 같다.
proxy_buffer_size 4k; proxy_buffers 8 4k; proxy_busy_buffers_size 8k; # 지정하지 않으면 자동 계산됨
여기서 중요한 규칙이 proxy_busy_buffers_size의 자동 계산과 검증 조건이다. 공식 문서와 소스 기준으로 세 가지를 기억하면 된다.
- 기본값(자동):
proxy_busy_buffers_size를 명시하지 않으면2 × max(proxy_buffer_size, proxy_buffers 한 장 크기)로 계산된다. - 하한:
max(proxy_buffer_size, proxy_buffers 한 장 크기)이상이어야 한다. - 상한:
proxy_buffers전체에서 한 장을 뺀 크기, 즉(개수 - 1) × 한 장 크기미만이어야 한다.
이 세 규칙이 다음 절의 함정을 만든다.
4. proxy_buffer_size만 올리면 nginx -t가 거부하는 이유
502를 없애려면 proxy_buffer_size를 키우는 게 맞다. 그래서 이것만 손대고 싶어진다.
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_buffer_size 16k; # 이거 하나만 올림
}
이 상태로 문법 검사를 돌리면 표준 리눅스(페이지 4KB, 기본 proxy_buffers 8 4k)에서는 통과하지 못한다.
$ sudo nginx -t nginx: [emerg] "proxy_busy_buffers_size" must be less than the size of all "proxy_buffers" minus one buffer in /etc/nginx/conf.d/sarc.conf:14 nginx: configuration file /etc/nginx/nginx.conf test failed
계산을 따라가 보면 이유가 분명하다. proxy_busy_buffers_size를 명시하지 않았으니 Nginx가 자동으로 2 × max(16k, 4k) = 32k로 잡는다. 그런데 proxy_buffers는 기본값 8 4k 그대로라 상한이 (8 - 1) × 4k = 28k다. 32k는 28k를 넘으므로 즉시 emerg로 거부된다.
여기서 한 번 걸렸다. proxy_buffer_size만 올려도 502가 사라진다는 식의 설명을 그대로 따라 했다가 reload 자체가 막히는 것이다. proxy_buffer_size를 키우면 proxy_buffers와 proxy_busy_buffers_size도 같이 손봐야 한다.
5. 올바른 설정
5-1. 세 지시어를 함께 맞춘 최소 처방
nginx -t를 통과하면서 큰 응답 헤더를 받는 최소 구성은 세 값을 일관되게 올리는 것이다.
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_buffer_size 16k; # 헤더 한 장을 키움
proxy_buffers 8 16k; # 본문 버퍼도 같은 장 크기로
proxy_busy_buffers_size 32k; # 명시적으로 지정
}
이 값들이 규칙을 만족하는지 검산해 보자. busy의 하한은 max(16k, 16k) = 16k, 상한은 (8 - 1) × 16k = 112k다. 지정한 32k는 16k 이상이고 112k 미만이므로 조건 안에 든다. 문법 검사를 돌려 확인한다.
$ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration test is successful $ sudo systemctl reload nginx
핵심은 502를 없애는 값이 proxy_buffer_size라는 점이다. 응답 헤더 덩어리가 이 한 장(여기서는 16k) 안에만 들어오면 502는 사라진다. proxy_buffers와 proxy_busy_buffers_size는 검증을 통과시키기 위해 함께 맞춰 주는 값이다.
5-2. 헤더가 더 클 때
2절의 curl로 잰 헤더가 16k도 넘는다면(예: JWT 여러 개가 쿠키로 쪼개져 내려오는 구성), 한 장을 더 키운다. 예를 들어 32k까지 잡으려면 다음과 같이 한다.
proxy_buffer_size 32k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;
검산: busy 하한 max(32k, 32k) = 32k, 상한 (8 - 1) × 32k = 224k. 지정한 64k가 범위 안이다. proxy_buffer_size는 실제로 측정한 헤더 크기보다 한 단계 여유를 두고 잡는 편이 안전하다. 응답 헤더 크기는 세션 상태나 쿠키 개수에 따라 늘어날 수 있기 때문이다.
5-3. 어디에 두는가
이 세 지시어는 http, server, location 블록에 둘 수 있다. 헤더가 큰 특정 경로만 문제라면 그 location에만 두는 게 영향 범위가 좁아 낫다. 프록시하는 백엔드 전체가 큰 헤더를 쓴다면 server 블록에 한 번만 적어 상속시킨다.
6. 검증
reload 후 문제의 경로를 다시 때려 상태 코드를 확인한다.
$ curl -s -o /dev/null -w '%{http_code}\n' https://sarc.io/api/me \
-H 'Cookie: session=...(실제 세션)...'
200
502가 200으로 바뀌고 error.log에 too big header가 더는 찍히지 않으면 해결된 것이다. 재발 감시는 로그를 한 줄로 거는 게 편하다.
$ sudo tail -f /var/log/nginx/error.log | grep --line-buffered 'too big header'
7. 함정 정리
- proxy_buffer_size만 올리지 마라. 표준 리눅스에서
proxy_busy_buffers_size자동값이 상한을 넘어nginx -t가 emerg로 거부한다. 세 값을 함께 맞춘다. - busy 범위를 검산하라. 하한은
max(proxy_buffer_size, proxy_buffers 한 장), 상한은(proxy_buffers 개수 - 1) × 한 장 크기다. 명시적으로 지정하면 자동 계산에 휘둘리지 않는다. - 본문이 아니라 헤더 문제다. 에러 문구의
reading response header를 놓치지 마라. 본문 버퍼링을 아무리 키워도 502는 안 사라진다. - 요청 헤더 400과 헷갈리지 마라. 큰 쿠키가 요청 방향에서 400/431을 낸다면 그건
large_client_header_buffers영역이다.
정리하면, 이 502는 업스트림 응답 헤더가 proxy_buffer_size 한 장을 넘어서 생긴다. curl로 헤더 크기를 재서 그보다 여유 있게 proxy_buffer_size를 잡고, proxy_buffers의 장 크기를 같게 올린 뒤, proxy_busy_buffers_size를 범위 안 값으로 명시하면 된다.