운연환경 구성과정에서 host 헤더를 설정하여 http 요청을 날리는 경우 리다이렉트시 요청 주소가 아닌 host 헤더에 설정된 값으로 응답이 와 문제가 생겼습니다.
결국 /etc/hosts 설정을 통해 문제를 해결하였지만 제가 생각했던 실행방식과 다르기에 이를 공부해봤습니다.
스프링부트에서 리다이렉트 시 Host 헤더가 어떻게 처리되는지, 그리고 X-Forwarded-Host 헤더가 어떤 영향을 미치는지 실제 코드를 통해 확인해보겠습니다.
간단한 스프링 부트 프로젝트를 아래와 같이 만들어 줍니다.
package com.dono.mdmpractice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
public class TestController {
@GetMapping("/info")
public String getBasicInfo(HttpServletRequest request) {
String host = request.getHeader("Host");
String xForwardedHost = request.getHeader("X-Forwarded-Host");
String xForwardedProto = request.getHeader("X-Forwarded-Proto");
String xForwardedPort = request.getHeader("X-Forwarded-Port");
StringBuilder response = new StringBuilder();
response.append("=== Request Header Info ===\n");
response.append("Host: ").append(host).append("\n");
response.append("X-Forwarded-Host: ").append(xForwardedHost).append("\n");
response.append("X-Forwarded-Proto: ").append(xForwardedProto).append("\n");
response.append("X-Forwarded-Port: ").append(xForwardedPort).append("\n");
response.append("\n=== Request URL Info ===\n");
response.append("getServerName(): ").append(request.getServerName()).append("\n");
response.append("getServerPort(): ").append(request.getServerPort()).append("\n");
response.append("getScheme(): ").append(request.getScheme()).append("\n");
response.append("getRequestURL(): ").append(request.getRequestURL()).append("\n");
log.info("Basic GET request - Host: {}, X-Forwarded-Host: {}", host, xForwardedHost);
return response.toString();
}
@GetMapping("/redirect")
public RedirectView testRedirect(HttpServletRequest request) {
String host = request.getHeader("Host");
String xForwardedHost = request.getHeader("X-Forwarded-Host");
log.info("Redirect request - Host: {}, X-Forwarded-Host: {}", host, xForwardedHost);
log.info("Request URL before redirect: {}", request.getRequestURL());
log.info("getServerName(): {}, getScheme(): {}", request.getServerName(), request.getScheme());
return new RedirectView("/redirected");
}
@GetMapping("/redirected")
public String redirected() {
return "Redirected successfully!";
}
}
이제 curl을 날려서 host헤더 설정이 어떠한 역할을 미치는지 확인을 해보겠습니다.
요청
curl --location 'http://localhost:8080/redirect'
응답
302응답이 옵니다
Response Headers
Location: http://localhost:8080/redirected
Content-Language: ko-KR
Content-Length: 0
Date: Wed, 27 Aug 2025 04:59:39 GMT
Keep-Alive: timeout=60
Connection: keep-alive
Host 헤더를 명시하지 않으면 기본적으로 요청 URL의 도메인(localhost:8080)이 사용됩니다.
요청
curl --location 'http://localhost:8080/redirect' \
--header 'Host: first.co.kr'
응답
같은 302 응답이 오지만 Location이 변경된 것을 확인할 수 있습니다.
Response Headers
Location: http://first.co.kr/redirected
Content-Language: ko-KR
Content-Length: 0
Date: Wed, 27 Aug 2025 05:01:05 GMT
Keep-Alive: timeout=60
Connection: keep-alive
Host 헤더를 설정하면 리다이렉트 Location에 해당 호스트가 반영됩니다.
요청
curl --location 'http://localhost:8080/redirect' \
--header 'Host: first.co.kr' \
--header 'X-Forwarded-Host: second.co.kr'
응답
Response Headers
Location: http://second.co.kr/redirected
Content-Language: ko-KR
Content-Length: 0
Date: Wed, 27 Aug 2025 05:02:08 GMT
Keep-Alive: timeout=60
Connection: keep-alive
X-Forwarded-Host 헤더가 존재하면 Host 헤더보다 우선순위가 높습니다.
RedirectView 클래스의 sendRedirect() 메소드를 보면 실제 리다이렉트 처리 과정을 확인할 수 있습니다.

sendRedirect()를 보면 먼저 http1.0 호환성 체크를 합니다.(기본값은 true입니다)
statusCode를 확인하게 되는데 해당 statusCode는 RedirectView를 통해 직접 상태 코드를 지정한 경우에 처리가 됩니다.else if (attributeStatusCode != null) 문의 경우에는 @ResponseStatus처럼 직접 상태 코드를 지정한 경웨 처리를 하게 됩니다,response.sendRedirect()를 타게 되는데 해당 메소드를 통과하면 Tomcat에서 redirect 관련 설정을 처리하게 됩니다.위 sendRedirect()가 호출 되면 tomcat의 Response 클래스가 호출됩니다.
톰캣의 Response 클래스의 sendRedirect() 메소드에서 setHeader를 통해 Location을 지정해줍니다.

핵심 로직을 보면:
if (getRequest().getCoyoteRequest().getSupportsRelativeRedirects() &&
(context == null || context.getUseRelativeRedirects())) {
locationUri = location;
} else {
locationUri = toAbsolute(location);
}
여기서 두 가지 조건을 확인합니다:
getRequest().getCoyoteRequest().getSupportsRelativeRedirects(): 클라이언트가 상대경로를 해석할 수 있는지 판단context == null || context.getUseRelativeRedirects(): 서버에서 상대경로 리다이렉트를 허용하는지 판단실제로 디버그를 하고 돌려보면:
getRequest().getCoyoteRequest().getSupportsRelativeRedirects() → truecontext == null || context.getUseRelativeRedirects() → false스프링 부트 애플리케이션은 임베디드 톰캣을 사용하며, 웹 애플리케이션이 구동될 때 Context 객체는 항상 생성되기에 context != null입니다. 그리고 context.getUseRelativeRedirects()의 경우에는 명시적으로 server.tomcat.use-relative-redirects=true를 설정하지 않는 이상 기본값이 false입니다.
결국 toAbsolute() 메소드로 가게 되는데, 이 메소드는 host, port 등을 조합해서 최종적인 절대 경로 location을 만들어서 반환합니다.

new RedirectView("/redirected") 생성 및 반환render 메서드 호출render 메서드에서 renderMergedOutputModel 호출createTargetUrl로 URL 생성, sendRedirect로 실제 리다이렉트 실행sendRedirect 받아서 최종적으로 Location 헤더 설정프록시나 로드밸런서 뒤에서 스프링부트 애플리케이션을 운영할 때는 다음 설정이 중요합니다:
server.forward-headers-strategy=NATIVE
이 설정을 통해 X-Forwarded-* 헤더들을 자동으로 처리하도록 합니다. NATIVE로 설정하면 톰캣이 자체적으로 X-Forwarded 헤더를 인식하고 처리합니다. (기본값은 NONE)
server.tomcat.use-relative-redirects=true
이 설정을 true로 하면 절대경로가 아닌 상대경로로 리다이렉트가 가능합니다. 프록시 환경에서는 프록시가 알아서 호스트 정보를 처리하므로 상대경로 리다이렉트가 유용할 수 있습니다. (기본값은 false입니다.)
스프링부트에서 Host 헤더와 X-Forwarded-Host 헤더는 리다이렉트 URL 생성에 직접적인 영향을 미칩니다.
실제 운영 환경에서는 server.forward-headers-strategy=NATIVE 설정을 통해 자동으로 처리하도록 하는 것이 권장되며, 필요에 따라 상대경로 리다이렉트 옵션도 고려해볼 수 있습니다.