본문 바로가기
삵

nginx 버퍼를 늘려도 특정 사용자만 400 Bad Request가 납니다

조회 4 · 댓글 1
영영웅문작성자2026년 10월 19일

사내 포털을 nginx 뒤 스프링부트(3.2, embedded Tomcat 10.1, JDK 17)로 운영하고 있습니다. 평소엔 멀쩡한데 SSO 연동을 붙인 뒤부터 일부 사용자만 로그인 직후 간헐적으로 400 Bad Request를 받습니다. 브라우저에는 짧은 "Bad Request" 화면만 뜨고, 같은 계정도 시크릿 창에서는 정상적으로 들어가집니다.

nginx access 로그에는 해당 요청이 400으로 찍혀 있는데 error 로그에는 아무것도 안 남습니다. 그래서 nginx 쪽 헤더 버퍼부터 늘려봤습니다.

large_client_header_buffers 4 32k;
client_header_buffer_size 16k;

그래도 동일한 사용자들은 계속 400을 받습니다. 톰캣 로그를 debug로 올려서 보니 이런 스택이 찍힙니다.

java.lang.IllegalArgumentException: Request header is too large
    at org.apache.coyote.http11.Http11InputBuffer.fill(Http11InputBuffer.java:802)
    at org.apache.coyote.http11.Http11InputBuffer.parseHeader(Http11InputBuffer.java:501)
    at org.apache.coyote.http11.Http11InputBuffer.parseHeaders(Http11InputBuffer.java:...)

문제가 나는 사용자들은 공통적으로 브라우저에 쿠키가 많이 쌓여 있거나, 속한 그룹이 많아 인증 토큰이 긴 편입니다. 쿠키를 전부 지우면 한동안은 괜찮아집니다. nginx에서 버퍼를 키워도 톰캣이 거부하는 것 같은데, 스프링부트처럼 server.xml이 따로 없는 embedded Tomcat 구조에서 이 한계를 올리는 설정이 어디에 있는지 궁금합니다. nginx와 톰캣 양쪽을 다 맞춰줘야 하는 건지도 알고 싶습니다.

appsrootappsroot2026년 10월 20일

증상만 보면 nginx가 아니라 뒤쪽 톰캣이 헤더를 거부하고 있는 상황입니다. 톰캣 로그의 IllegalArgumentException: Request header is too large가 결정적인 근거입니다. nginx error 로그에 아무것도 안 남고 access 로그에만 400이 찍히는데도 톰캣에 debug 스택이 올라온다는 건, nginx는 요청을 정상으로 받아 업스트림으로 넘겼고 400을 만든 주체가 embedded Tomcat이라는 뜻입니다. 시크릿 창에서 멀쩡한 것, 쿠키를 지우면 한동안 괜찮은 것, 그룹이 많아 SSO 토큰이 긴 사용자만 터지는 것 모두 "요청 헤더(쿠키 포함) 총량이 톰캣 한계를 넘는다"는 한 가지 원인을 가리킵니다.

nginx large_client_header_buffers, client_header_buffer_size는 nginx가 클라이언트 요청 헤더를 읽어들일 때 쓰는 버퍼일 뿐입니다. 이 값을 키우면 nginx 단계에서의 400은 사라지지만, nginx가 그 큰 헤더를 그대로 업스트림으로 전달하면 톰캣이 자기 한계로 다시 거부합니다. 그래서 "nginx를 키워도 같은 사용자만 계속 400"이 되는 것이고, 양쪽을 다 맞춰줘야 한다는 추측이 맞습니다.

톰캣 10.1 공식 문서 기준으로 관련 속성은 두 개입니다. maxHttpRequestHeaderSize는 요청 라인과 헤더 전체의 최대 크기(바이트)이고, 지정하지 않으면 maxHttpHeaderSize 값을 따릅니다. 그리고 maxHttpHeaderSize의 기본값은 8192(8 KiB)입니다. 즉 지금 한계는 8KB이고, nginx쪽 32k와 맞지 않아 톰캣에서 걸리는 것으로 보입니다.

스프링부트는 server.xml이 없으니 application.properties(또는 yml)로 Connector 속성을 넘깁니다. 스프링부트 3.x에서는 server.max-http-header-size가 deprecated 되고 server.max-http-request-header-size로 바뀌었으니 이 키를 쓰시면 됩니다.

# application.properties (Spring Boot 3.2)
server.max-http-request-header-size=32KB

# application.yml 이면
server:
  max-http-request-header-size: 32KB

값은 nginx의 large_client_header_buffers 4 32k;와 동일하게 32KB로 맞추는 것을 권합니다. 두 구간의 한계가 어긋나면 한쪽을 넘기자마자 다시 막히기 때문입니다. 설정 후 sudo systemctl restart(또는 앱 재기동)로 반영하시고, 문제 사용자 계정으로 재로그인해 확인해보세요.

한 가지 주의할 점은 톰캣 공식 문서에도 경고가 있는데, 톰캣은 지정한 헤더 버퍼 크기를 요청마다 전부 할당합니다. 예로 1MB로 잡고 동시 요청 100개면 헤더만으로 100MB 힙을 먹습니다. 그래서 무작정 크게 잡지 말고 실제 필요한 최소치(32KB 정도)로 두는 게 안전합니다.

병행해서 근본 원인도 줄여두시길 권합니다. 특정 사용자만 터지는 건 SSO 토큰 길이 + 누적 쿠키가 합산돼 한계를 넘기 때문일 가능성이 큽니다. 그룹/권한을 쿠키나 헤더에 통째로 싣고 있다면 세션 참조 방식으로 바꾸거나, 불필요한 쿠키의 수명/스코프를 정리하면 재발을 줄일 수 있습니다.

확인은 이렇게 해보시면 됩니다. nginx access 로그 포맷에 $http_cookie 또는 요청 헤더 길이를 임시로 찍어 실제 크기를 보거나, 문제 사용자 요청을 브라우저 개발자도구 Network 탭에서 Request Headers 크기로 직접 재보면 8KB를 넘는지 바로 드러납니다. 설정 적용 전후로 톰캣 debug 로그에서 같은 스택이 사라지는지도 같이 보시면 확실합니다.

참고: Apache Tomcat 10.1 HTTP Connector 설정 레퍼런스, Spring Boot Common Application Properties

🔗 https://tomcat.apache.org/tomcat-10.1-doc/config/http.html

🔗 https://docs.spring.io/spring-boot/appendix/application-properties/index.html

로그인 후 답글을 남길 수 있습니다.