리버스 프록시(nginx) 뒤에 Spring WebFlux/Reactor Netty 같은 WAS가 있는 구조에서 414 (Request-URI Too Long)가 발생하면, nginx 버퍼만 키워서는 해결되지 않을 수 있다. 요청 라인(request line) 길이 한계가 프록시와 WAS 양쪽에 독립적으로 존재하기 때문이다.
414 Request-URI Too Long은 요청 라인(GET /path?query HTTP/1.1)의 길이가 서버가 허용하는 한계를 초과할 때 반환된다. 직렬화된 JSON을 URL-encode해서 쿼리 파라미터로 싣는 경우처럼, 쿼리 길이가 누적으로 늘어나는 상황이 대표적이다.
핵심은 이 한계가 요청을 거치는 각 레이어마다 따로 존재한다는 점이다. nginx → WAS 구조라면 nginx의 한계와 WAS의 한계가 별개이고, 둘 중 더 작은 쪽이 실질 한계가 된다. 실제 배포에서는 CDN / L7 로드밸런서 / API 게이트웨이 → nginx → WAS처럼 앞단이 더 있을 수 있고, 이들도 각자 URL·헤더 길이 한계를 가지므로 nginx보다 더 앞단에서 414를 낼 수도 있다 — 414를 만나면 어느 레이어든 후보다.
이 문서의 "요청 라인" 개념은 HTTP/1.1 기준이다. HTTP/2에는 요청 라인이 없고 URI가
:path의사 헤더(HPACK 압축)로 전달되므로, 한계가maxInitialLineLength가 아니라 헤더 목록/프레임 크기 설정(SETTINGS_MAX_HEADER_LIST_SIZE등)의 영향을 받는다. HTTP/2 경로에서 414를 만나면 점검 대상이 달라진다.
nginx는 요청 라인과 헤더를 두 가지 버퍼 설정으로 제한한다.
| 지시어 | 기본값 | 역할 |
|---|---|---|
client_header_buffer_size |
1k |
요청 라인 + 헤더를 처음 읽는 버퍼 |
large_client_header_buffers |
4 8k |
위 버퍼를 넘으면 사용하는 큰 버퍼 (개수 / 1개 크기) |
large_client_header_buffers의 버퍼 1개 크기 안에 들어가야 한다. 넘으면 414.상향 예시:
client_header_buffer_size 16k;
large_client_header_buffers 8 32k;
확인 방법:
# 실행 중인 인스턴스의 실효 설정 확인
nginx -T | grep -E 'client_header_buffer_size|large_client_header_buffers'
변경 적용:
nginx -t # 설정 문법 검증
nginx -s reload # 무중단 반영
server_tokens off이면 응답Server헤더에 버전이 표기되지 않는다. 또한 프록시가 상위(upstream) 응답을 relay할 때도 자신의Server: nginx를 붙이므로,Server: nginx만 보고 414를 nginx가 냈다고 단정하면 안 된다.
Spring WebFlux의 기본 내장 서버인 Reactor Netty도 요청 라인/헤더 한계를 독립적으로 가진다.
| 항목 | 기본값 | 의미 |
|---|---|---|
maxInitialLineLength |
4096 (4KB) |
요청 라인(메서드 + URI + 버전) 최대 길이 → 초과 시 414 |
maxHeaderSize |
8192 (8KB) |
헤더 블록 총량 |
함정: Spring Boot의 server.max-http-request-header-size 속성은 maxHeaderSize만 설정한다. URI는 요청 라인에 들어가므로, 이 값을 키워도 maxInitialLineLength(요청 라인 한계)는 그대로 4096이다. 즉 "헤더 크기는 키웠는데 긴 URL은 여전히 414"인 상황이 벌어진다.
Spring Boot 3.x는 요청 라인 한계를 별도 속성으로 노출한다:
server:
netty:
max-initial-line-length: 32KB # 요청 라인 한계 (기본 4KB)
max-http-request-header-size: 32KB # 헤더 총량 (별개 설정)
이 속성은 프레임워크가 내부적으로 Reactor Netty의 httpRequestDecoder().maxInitialLineLength(...)로 직접 배선하므로, 별도 코드가 필요 없다.
속성이 없던 구버전에서는
WebServerFactoryCustomizer로maxInitialLineLength를 직접 설정해야 했고, 이때 프레임워크 기본 커스터마이저가 사용자 설정을 덮어쓰는 이슈가 알려져 있었다. 최신 버전에서는 속성 한 줄로 충분하므로, 먼저 사용 중인 버전이 속성을 지원하는지 확인하는 편이 안전하다.
nginx 버퍼를 32k로 키워도, 그 뒤 WAS의
maxInitialLineLength가 4096이면 WAS가 414를 반환한다. nginx는 이 응답을 그대로 클라이언트에 relay하면서 자기Server: nginx를 붙일 뿐이다. 겉보기엔 nginx가 낸 414처럼 보이지만 실제 원인은 WAS다.
따라서 414를 만나면 양쪽을 모두 점검해야 한다. nginx만 고치면 "분명히 버퍼를 키웠는데 여전히 414"라는 함정에 빠진다. 반대로 WAS만 키우고 nginx가 더 작은 한계를 가지면 nginx에서 막힌다 — 결국 두 레이어의 한계를 함께 정렬하는 것이 정답이다.
프록시를 우회해 WAS를 직접 호출해 비교한다.
# 1) 프록시 경유 (예: 80 포트)
curl -sI 'http://<host>/<긴 URL>'
# → 414 / Server: nginx / Connection: keep-alive
# 2) WAS 직접 호출 (프록시 우회, 애플리케이션 포트)
curl -sI 'http://localhost:<app-port>/<긴 URL>'
# → 414 / (Server 헤더 없음) / connection: close
판별 포인트:
Server 헤더를 붙이지 않고, 요청 라인 프레이밍 에러 시 연결을 닫는다(connection: close). 프록시 경유 응답의 Server: nginx / keep-alive와 대비된다.한계 상향은 임시방편이다. 데이터가 계속 늘면 다시 한계에 부딪힌다.
한계 상향(버퍼 / 요청 라인)으로 즉시 unblock하고, URL 구조 개선으로 재발을 막는 2단계 접근이 현실적이다.