본문으로 건너뛰기
김신건의 로그

[Spring Boot] Embedded Web Server 설정과 튜닝

· 수정 · 📖 약 2분 · 582자/단어 #spring-boot #tomcat #netty #jetty #undertow #performance #tuning
Spring Boot Embedded Server, Tomcat tuning, Netty tuning, Jetty tuning, Undertow tuning, WebServerFactoryCustomizer, server.port, 내장 웹 서버

정의

Spring Boot 의 starter 가 내장 서버를 함께 번들. 별도 WAR 배포 / 외부 Tomcat 불필요.

기본 매핑:

  • spring-boot-starter-webTomcat (기본)
  • spring-boot-starter-webfluxNetty
  • spring-boot-starter-jettyJetty
  • spring-boot-starter-undertowUndertow

같은 starter 안에서 교체:

implementation("org.springframework.boot:spring-boot-starter-web") {
    exclude(module = "spring-boot-starter-tomcat")
}
implementation("org.springframework.boot:spring-boot-starter-jetty")

기본 설정

server:
  port: 8080
  servlet:
    context-path: /api          # 모든 경로 prefix
  forward-headers-strategy: native   # reverse proxy 뒤 X-Forwarded-* 처리
  compression:
    enabled: true
    mime-types: text/html, application/json
    min-response-size: 1024
  http2:
    enabled: true
  error:
    whitelabel:
      enabled: false

server.port=0 면 random port (테스트에서 흔히 사용).

Tomcat 튜닝

server:
  tomcat:
    threads:
      max: 200                  # 동시 처리 thread
      min-spare: 10
    accept-count: 100           # 큐
    max-connections: 8192       # 동시 연결
    connection-timeout: 20s
    keep-alive-timeout: 60s
    max-keep-alive-requests: 100
    max-http-form-post-size: 2MB
    max-swallow-size: 2MB       # 거부 요청의 본문 읽기 제한
    uri-encoding: UTF-8
    accesslog:
      enabled: true
      pattern: "%t %a \"%r\" %s %b"
      directory: /var/log/tomcat

threads.max 의미

worker thread 수. 요청마다 1 thread 점유. Tomcat 의 blocking 모델이므로:

  • low-traffic, 빠른 응답: 50~200
  • high-traffic, DB call 느림: 500~1000
  • virtual thread 활성화 시 (Spring Boot 3.2+): 의미 사라짐

max-connections vs threads.max

max-connections > threads.max 가 정상. 연결은 keep-alive 로 유지되지만 worker 가 부족하면 큐잉. accept-count 만큼 큐.

virtual thread (Spring Boot 3.2+)

spring:
  threads:
    virtual:
      enabled: true

Tomcat worker pool 이 virtual thread 사용. threads.max 영향 없음. spring-virtual-threads 참고.

Netty 튜닝 (WebFlux)

server:
  netty:
    connection-timeout: 20s
    idle-timeout: 60s
    max-keep-alive-requests: 1000
spring:
  reactor:
    netty:
      pool:
        max-connections: 10000
        max-idle-time: 30s

Netty 는 이벤트 루프 기반. thread 수 적음 (보통 CPU core × 2). 동시 처리는 connection 수가 결정.

WebFlux + Netty 가 reactive non-blocking 시 가장 자연스러움. spring-webflux 참고.

Jetty / Undertow

server:
  jetty:
    threads:
      max: 200
      min: 8
    accesslog:
      enabled: true
  undertow:
    threads:
      io: 4
      worker: 64
    buffer-size: 16384

Undertow 는 XNIO 기반. Tomcat 보다 메모리 효율적, throughput 우수.

다이나믹 customization

application.yml 로 안 되는 세밀 조정:

@Component
public class TomcatCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        factory.addConnectorCustomizers(connector -> {
            connector.setProperty("relaxedQueryChars", "[]|");   // 특수문자 허용
            connector.setProperty("disableUploadTimeout", "false");
            connector.setProperty("connectionUploadTimeout", "120000");
        });

        factory.addContextCustomizers(context -> {
            context.setCookieProcessor(new Rfc6265CookieProcessor());
        });

        factory.setProtocol("org.apache.coyote.http11.Http11AprProtocol");   // APR
    }
}

WebServerFactoryCustomizer<T> Bean 으로 등록. 시작 시 자동 적용.

동적 port

@SpringBootApplication
class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(App.class, args);
        int port = ctx.getWebServer().getPort();
        System.out.println("Started on port " + port);
    }
}

테스트:

@SpringBootTest(webEnvironment = RANDOM_PORT)
class IntegrationTest {
    @LocalServerPort int port;   // 자동 주입
}

Graceful Shutdown

server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s
  • 신규 연결 거부
  • 진행 중 요청 완료 대기 (최대 30s)
  • 이후 강제 종료

K8s terminationGracePeriodSeconds 와 맞춤. preStop hook 도 활용.

HTTP/2

server:
  http2:
    enabled: true
  ssl:
    enabled: true
    key-store: classpath:keystore.p12
    key-store-password: ${KEYSTORE_PASSWORD}

HTTP/2 는 보통 SSL 필요 (h2). plaintext HTTP/2 (h2c) 는 reverse proxy 뒤.

Compression

server:
  compression:
    enabled: true
    mime-types:
      - text/html
      - text/xml
      - text/plain
      - text/css
      - text/javascript
      - application/javascript
      - application/json
    min-response-size: 1024

응답 크기 > 1KB 이면 gzip. JSON / HTML / CSS 압축으로 대역폭 절약.

CDN / reverse proxy 가 압축한다면 끄기.

CORS (전역)

@Configuration
class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("https://app.example.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowedHeaders("*")
            .allowCredentials(true)
            .maxAge(3600);
    }
}

Spring Security 도 함께 사용 시 SecurityFilterChain 에 cors 활성화 필요:

.cors(c -> c.configurationSource(corsSource))

Static resources

spring:
  web:
    resources:
      static-locations: classpath:/static/, classpath:/public/
      cache:
        period: 30d
        cachecontrol:
          max-age: 30d
          must-revalidate: true
      add-mappings: true
mvc:
  static-path-pattern: /static/**

production: long cache + content hash (Vite / webpack [name].[hash].js).

Error pages

server:
  error:
    whitelabel:
      enabled: false
    include-message: always
    include-binding-errors: always
    include-stacktrace: on-param   # ?trace=true 일 때만
    include-exception: false

전역 @ControllerAdvice 또는 ErrorController 로 custom.

모니터링

management:
  endpoints:
    web:
      exposure:
        include: health, metrics, info, threaddump
  metrics:
    enable:
      tomcat: true
      jvm: true

Tomcat 의 thread pool / connection 상태 → Prometheus / Grafana.

함정과 베스트 프랙티스

  • production: graceful shutdown + 적절한 timeout
  • Tomcat threads.max 는 DB connection pool size 와 균형 (worker > pool)
  • CDN / reverse proxy 뒤forward-headers-strategy: native 필수
  • HTTPS terminate 위치: 보통 reverse proxy. Spring 은 plain HTTP 받음
  • virtual thread 활성화 시 worker pool 튜닝 무의미해짐
  • embedded server 선택: 대부분 기본 Tomcat 으로 충분. 특수 요구 (메모리 ↓, 비동기 ↑) 시만 교체
  • Netty 와 WebFlux 페어: blocking 코드 섞으면 의미 없음. 모든 단계 reactive 여야

관련 위키

이 글의 용어 (6개)
[Spring Boot] 자동 구성과 @SpringBootApplicationspring
정의 Spring Boot의 핵심 가치 = 자동 구성(autoconfiguration). classpath의 라이브러리를 보고 합리적 기본 설정으로 Bean 등록. Tomcat이…
[Spring Boot] SSL/HTTPS 설정spring
정의 Spring Boot 에서 HTTPS / TLS 설정 방법. JKS (legacy), PKCS12, PEM (modern), SSL Bundles (Spring Boot 3…
[Spring] Actuator: health, metrics, infospring
정의 Spring Boot Actuator는 프로덕션용 monitoring/management endpoint 자동 제공. health check, metrics, env, be…
[Spring] Spring MVC: @RestController, @RequestMappingspring
정의 Spring MVC는 Servlet 기반 동기 웹 프레임워크. 이 요청을 받아 핸들러(컨트롤러 메서드)에 라우팅. REST API와 전통 MVC(HTML) 모두 지원. We…
[Spring] Virtual Threads (Project Loom): Spring 6.1+spring
정의 Virtual Thread 는 JDK 21+ 에서 GA 된 경량 thread. JVM 이 OS thread (carrier) 위에 가상 thread 를 M:N 매핑. 수백만…
[Spring] WebFlux: Reactive, Mono, Fluxspring
정의 Spring WebFlux는 Project Reactor 기반의 비동기·non-blocking 웹 프레임워크. Spring MVC와 별개. Netty 기본 (Tomcat도 …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기