마이크로서비스 아키텍처에서 API Gateway는 클라이언트 요청을 적절한 서비스로 라우팅하는 중요한 역할을 합니다. 이 게이트웨이에서 발생하는 문제는 모든 트래픽이 영향을 받기 때문에 시스템 전체 가용성에 직접적인 영향을 미칩니다. 최근 Spring Cloud Gateway에서 X-Forwarded-Prefix 헤더가 요청 경로에 영향을 미치는 이슈를 발견했습니다. 이 글에서는 문제 상황부터 원인 분석, 해결 방법까지 상세히 공유하고자 합니다.
1. 문제 상황: 예상치 못한 라우팅 경로 변경
Spring Cloud Gateway를 기반으로 한 API Gateway를 개발 환경에서 테스트하던 중, 이상한 현상을 마주쳤습니다. X-Forwarded-Prefix 헤더가 포함된 요청을 보냈을 때, Gateway가 이 헤더 값을 경로 앞에 자동으로 붙여서 라우팅을 시도하는 일이었죠.
발생 시나리오
예를 들어, /test/** 경로에 대한 라우트가 설정된 상태에서 아래와 같은 요청을 보냈습니다.
curl --location 'http://localhost:8080/test/hello' \
--header 'X-Forwarded-Prefix: /x-forwarded-prefix'
기대했던 대로 /test/hello로 라우팅되기를 바랐지만, 실제로는 /x-forwarded-prefix/test/hello로 라우팅이 시도되며 404 에러가 발생했습니다.
Gateway 로그를 확인해보니 요청 경로가 바뀐 흔적이 보였습니다.
17:35:21.205 [reactor-http-nio-3] DEBUG |o.s.w.s.a.HttpWebHandlerAdapter: [/] [0362ff24-1] HTTP GET "/x-forwarded-prefix/test/hello"
환경별 차이점
흥미로운 점은 이 문제가 모든 환경에서 똑같이 발생하지 않았다는 거예요. 아래처럼 환경별로 차이가 있었습니다.
환경 | Spring Cloud 버전 | 문제 발생 여부 |
개발 환경 (쿠버네티스) | 2024.0.0 | ⚠️ 발생 |
로컬 환경 | 2024.0.0 | ✅ 정상 작동 |
개발 환경 (쿠버네티스) | 2023.0.0 | ✅ 정상 작동 |
이러한 차이는 왜 발생했을까요? 🧐
2. 원인 분석
2-1. 첫 번째 접근: X-Forwarded 헤더 관련 설정 검토 (해결 X)
먼저 X-Forwarded 헤더 관련 설정을 확인해봤습니다. Spring Cloud Gateway의 설정 옵션은 다음과 같습니다.
spring:
cloud:
gateway:
x-forwarded:
prefix-enabled: true # X-Forwarded-Prefix를 활성화할지 (기본값: true)
prefix-append: true # 헤더 값을 추가할지 (기본값: true)
위 설정값을 처리하는 XForwardedHeadersFilter 소스코드를 보니, 이 설정들은 헤더 자체를 어떻게 처리할지에 초점이 맞춰져 있을 뿐, 헤더 값을 경로에 추가하는 동작과는 직접적인 관련이 없었습니다.
private void updateRequest(HttpHeaders updated, URI originalUri, String originalUriPath, String requestUriPath) {
String prefix;
if (requestUriPath != null && (originalUriPath.endsWith(requestUriPath))) {
prefix = substringBeforeLast(originalUriPath, requestUriPath);
if (prefix != null && prefix.length() > 0 && prefix.length() <= originalUri.getPath().length()) {
write(updated, X_FORWARDED_PREFIX_HEADER, prefix, isPrefixAppend());
}
}
}
private void write(HttpHeaders headers, String name, String value, boolean append) {
if (value == null) {
return;
}
if (append) {
headers.add(name, value);
// these headers should be treated as a single comma separated header
List<String> values = headers.get(name);
String delimitedValue = StringUtils.collectionToCommaDelimitedString(values);
headers.set(name, delimitedValue);
}
else {
headers.set(name, value);
}
}
2-2. 두 번째 접근: 포워드 헤더 전략 검토 (해결)
그 다음 Spring Boot의 프록시 서버 설정 문서를 참고하며 server.forward-headers-strategy를 살펴봤습니다. 이 설정은 세 가지 값을 가질 수 있습니다.
- none: 포워드 헤더 무시
- native: 서버 네이티브 기능으로 처리
- framework: 프레임워크 레벨에서 처리
각 옵션을 테스트한 결과, server.forward-headers-strategy: none으로 설정했을 때 문제가 해결되었습니다.
3. 근본 원인: 버전과 환경 설정의 차이
그렇다면 왜 Spring Cloud 2024.0.0에서만 문제가 생겼을까요?
제가 사용한 환경은 쿠버네티스 기반이었고, Spring 공식 문서에 따르면 Cloud Platform 환경(쿠버네티스 포함)에서는 server.forward-headers-strategy의 기본값이 NATIVE로 설정된다고 합니다.
"If your application runs in a supported Cloud Platform, the server.forward-headers-strategy property defaults to NATIVE. In all other instances, it defaults to NONE."
디버깅 끝에 경로가 재작성되는 클래스를 발견했는데, 바로 ReactorUriHelper였습니다. 여기서 X-Forwarded-Prefix 값을 경로에 추가하는 로직이 포함되어 있습니다.

하지만 Spring Cloud 2023.0.0에서는 이런 재작성 로직이 없었습니다. Spring Web 6.1.5 버전에서는 URI 생성 시 X-Forwarded-Prefix를 경로에 반영하지 않았던 거죠.

로컬 환경은 왜 괜찮았나요?
로컬에서는 문제가 없었던 이유는 NettyWebServerFactoryCustomizer의 설정 로직 때문입니다.
NettyWebServerFactoryCustomizer 클래스의 getOrDeduceUseForwardHeaders 메서드는 포워드 헤더를 사용할지 여부를 결정하는 로직을 담고 있습니다.
public class NettyWebServerFactoryCustomizer {
private boolean getOrDeduceUseForwardHeaders() {
if (this.serverProperties.getForwardHeadersStrategy() != null) {
return this.serverProperties.getForwardHeadersStrategy().equals(ForwardHeadersStrategy.NATIVE);
} else {
CloudPlatform platform = CloudPlatform.getActive(this.environment);
return platform != null && platform.isUsingForwardHeaders();
}
}
}
- 먼저 serverProperties.getForwardHeadersStrategy()가 null이 아닌 경우, 설정값이 ForwardHeadersStrategy.NATIVE와 같은지 확인하여 반환합니다. 즉, 명시적으로 NATIVE로 설정된 경우 포워드 헤더 사용 여부를 결정합니다.
- serverProperties.getForwardHeadersStrategy()가 null인 경우, CloudPlatform.getActive(this.environment)를 통해 현재 실행 환경의 CloudPlatform을 가져옵니다.
- platform이 null이 아닌 경우, platform.isUsingForwardHeaders()는 무조건 true를 반환하므로 포워드 헤더를 사용하도록 설정됩니다. 이는 클라우드 환경(예: 쿠버네티스)에서 기본적으로 포워드 헤더가 활성화될 수 있음을 의미합니다.
결론적으로, 이 메서드는 설정값이 없으면 환경에 따라(CloudPlatform 기반) 포워드 헤더 사용 여부를 자동으로 유추하며, 로컬 환경에서는 CloudPlatform이 null이므로 포워드 헤더가 비활성화되는 동작을 보입니다.
server.forward-headers-strategy: framework 설정의 경우도 문제 발생
server.forward-headers-strategy를 framework로 설정해도 경로 재작성 문제가 해결되지 않았습니다. 이 경우 ReactorUriHelper에서는 경로 재작성이 일어나지 않지만, ForwardedHeaderTransformer라는 클래스가 경로를 수정하는 역할을 맡습니다.
이 클래스의 코드에서 빨간 네모로 표시된 부분을 보면, prefix 값을 기반으로 요청 경로를 재작성하는 로직이 포함되어 있습니다.

이처럼 prefix를 경로에 반영하는 동작은 NATIVE 설정과 유사하게 X-Forwarded-Prefix 헤더를 경로에 적용할 수 있습니다. ForwardedHeaderTransformer는 framework 설정이 적용될 때 자동으로 빈으로 등록되며, 이로 인해 경로 재작성 문제가 발생합니다.

4. 해결책
원인을 파악했으니 해결책도 명확합니다. application.yml 또는 application.properties에 아래 설정을 추가하면 됩니다.
server:
forward-headers-strategy: none
이 설정은 X-Forwarded-Prefix를 포함한 모든 포워드 헤더를 처리 과정에서 무시하도록 지시합니다. 따라서 ReactorUriHelper나 ForwardedHeaderTransformer가 경로를 재작성하는 동작이 발생하지 않아 의도한 대로 라우팅이 이루어집니다.
5. 실무 적용을 위한 가이드라인
이번 이슈를 통해 얻은 교훈을 정리하여 실무에 적용할 수 있는 가이드라인을 제시합니다.
- 환경별 기본 설정 차이 인지: 동일한 코드라도 실행 환경(로컬, 쿠버네티스 등)에 따라 기본값이 달라질 수 있으니 주의가 필요합니다.
- 명시적 설정 활용: 중요한 설정은 기본값에 의존하기보다는 명시적으로 지정하는 게 더 안전합니다. 특히 server.forward-headers-strategy와 같은 환경에 따라 기본값이 달라지는 설정은 항상 명시적으로 지정하세요.
- 버전 업그레이드 테스트 체크리스트:
- 공식 문서에서 Breaking Changes 확인
- 환경별 동작 차이 테스트
- 헤더 처리 동작 검증 (특히 X-Forwarded-* 헤더)
- 로컬과 클라우드 환경 모두에서 테스트
6. 결론
X-Forwarded-Prefix로 인한 라우팅 문제는 Spring Web 버전 업그레이드와 환경별 기본 설정 차이의 조합으로 발생한 것이었습니다. Spring Cloud Gateway를 사용할 때는 X-Forwarded-* 헤더의 처리 방식을 명확히 이해하고, 항상 명시적인 설정을 통해 개발 및 운영 환경 간의 일관성을 유지하는 것이 중요합니다.
[참고]