본문 바로가기
서버 인프라 실무

Tomcat 10 설치+튜닝 체크리스트: 커넥터/스레드풀/압축/보안 헤더 한 번에 정리

by yamoojin83 2025. 10. 16.

Tomcat 10 설치+튜닝 체크리스트: 커넥터/스레드풀/압축/보안 헤더 한 번에 정리

Tomcat을 “설치”하는 것과 “운영 가능한 설정”으로 만드는 것은 다릅니다. 본 글은 Tomcat 10.x 기준으로 커넥터/스레드풀/Keep-Alive/헤더 한도/압축/보안 헤더/액세스 로그를 실무 감각으로 정리합니다.


1) 환경 가정

  • OS: Ubuntu 24.04, JDK 17+
  • 배치: Nginx(80/443) ↔ Tomcat(127.0.0.1:8080)
  • Tomcat 설치 경로: /opt/tomcat, 서비스 계정 tomcat

모든 설정 파일은 /opt/tomcat/conf/server.xml/opt/tomcat/conf 하위 파일을 기준으로 설명합니다.


2) 커넥터(HTTP/1.1) 핵심 파라미터

기본 HTTP 커넥터 블록을 먼저 정리합니다. (NIO 기본)

<Connector 
  protocol="HTTP/1.1"
  address="127.0.0.1"
  port="8080"
  connectionTimeout="20000"
  maxThreads="200"
  minSpareThreads="20"
  acceptCount="100"
  maxKeepAliveRequests="100"
  keepAliveTimeout="15000"
  maxHttpRequestHeaderSize="8192"
  redirectPort="8443"
/>
  • address: 리버스 프록시 뒤에 둘 때는 127.0.0.1로 바인딩(외부 차단).
  • maxThreads: 요청을 처리하는 워커 스레드 수. CPU/백엔드 지연을 보며 100~400 사이에서 조정합니다.
  • minSpareThreads: 초기 대기 스레드. 과도하면 메모리 낭비, 너무 낮으면 콜드 스타트 지연.
  • acceptCount: 스레드가 바빠질 때 큐에 쌓을 수 있는 연결 수. maxThreads의 0.5~1배 권장.
  • maxKeepAliveRequests: Keep-Alive로 재사용 가능한 요청 수. 100~1000 범위에서 트래픽 특성으로 조정.
  • keepAliveTimeout: 유휴 연결을 유지할 시간(ms). 너무 길면 FD 고갈, 너무 짧으면 재연결 증가.
  • maxHttpRequestHeaderSize: 요청 헤더 최대 바이트. 기본 8KB. 인증·쿠키가 큰 서비스는 16KB도 고려.

팁: 프록시(Nginx)의 proxy_read_timeout/proxy_send_timeout과 값이 지나치게 어긋나면 499/504가 늘어납니다. 프록시/백엔드 타임아웃을 한 표로 관리하세요.


3) 압축(Gzip) 설정

정적/JSON 응답에 압축을 켜면 대역폭이 크게 줄지만, 압축 임계치MimeType을 잘 잡아야 CPU 낭비를 막을 수 있습니다. (프록시에서 압축을 끝내는 편이 보통 더 효율적이지만, Tomcat 단에서도 가능)

<Connector ... 
  compression="on"
  compressionMinSize="1024"
  noCompressionUserAgents="gozilla, traviata"
  compressableMimeType="text/html,text/xml,text/plain,text/css,application/json,application/javascript,application/xml"
/>
  • compressionMinSize: 1KB 미만은 이득이 적으므로 압축 X.
  • compressableMimeType: JSON/CSS/JS/HTML 위주로. 이미지/zip/pdf 등 이미 압축된 포맷은 제외.
  • Nginx에서 gzip on;을 쓰고 Tomcat은 끄는 전략도 OK. 이중 압축은 안 됩니다.

4) 스레드·큐 튜닝 가이드(실무 감)

  • 기준치: maxThreads=200, acceptCount=100으로 시작.
  • APM에서 요청 시간 분포활성 스레드를 보며 CPU가 여유인데 대기 시간이 길면 maxThreads↑.
  • 반대로 GC·컨텍스트 스위칭이 가파르면 maxThreads↓하고 백엔드 지연 원인을 먼저 보세요.
  • acceptCount 과다는 문제 은닉을 유발. 과부하 시 빨리 실패가 낫습니다.

5) 헤더/바디 제한으로 안전망 만들기

너무 큰 헤더/바디는 메모리와 파서를 압박합니다.

<Connector ...
  maxHttpRequestHeaderSize="16384"
  maxSwallowSize="10485760"  > <!-- 10MB, 업로드가 크면 상향 -->
/>

파일 업로드는 보통 프록시에서 client_max_body_size 20m;로 먼저 제한하고, 애플리케이션에서 추가 검증을 합니다.


6) 보안 헤더(리버스 프록시에서 권장, 필터로도 가능)

Nginx에서 넣는 것이 가장 간단합니다.

# /etc/nginx/snippets/security-headers.conf
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-XSS-Protection "0" always;
add_header Permissions-Policy "geolocation=()" always;

Tomcat만 단독으로 쓸 경우엔 서블릿 필터로 추가할 수 있습니다.

// SecurityHeadersFilter.java
public class SecurityHeadersFilter implements javax.servlet.Filter {
  @Override public void doFilter(javax.servlet.ServletRequest req, javax.servlet.ServletResponse res,
                                 javax.servlet.FilterChain chain)
      throws java.io.IOException, javax.servlet.ServletException {
    var r = (javax.servlet.http.HttpServletResponse) res;
    r.setHeader("X-Content-Type-Options", "nosniff");
    r.setHeader("X-Frame-Options", "SAMEORIGIN");
    r.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
    r.setHeader("Permissions-Policy", "geolocation=()");
    chain.doFilter(req, res);
  }
}

7) 원본 클라이언트 IP 전달(X-Forwarded-For)

Nginx에서 헤더를 넘기고, 애플리케이션에서 신뢰 프록시를 인지해야 합니다.

# nginx location 안
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

스프링 부트라면 server.forward-headers-strategy=native 혹은 server.use-forward-headers=true 설정으로 반영합니다.


8) 액세스 로그(AccessLogValve) 패턴

응답 코드를 중심으로 병목을 찾기 쉬운 패턴입니다.

<Valve className="org.apache.catalina.valves.AccessLogValve"
       directory="logs" prefix="access" suffix=".log"
       pattern="%{yyyy-MM-dd HH:mm:ss}t %a "%r" %s %b %D %{X-Forwarded-For}i" />
  • %D: 처리 시간(마이크로초). p95/p99를 집계해 급증 시점 추적.
  • %a + X-Forwarded-For: 진짜 클라이언트 IP 확인.

9) 예시: 실무 기본 템플릿

아래 블록을 server.xml 커넥터에 적용해 시작점을 만들 수 있습니다.

<Connector protocol="HTTP/1.1"
  address="127.0.0.1"
  port="8080"
  connectionTimeout="20000"
  maxThreads="200"
  minSpareThreads="20"
  acceptCount="120"
  maxKeepAliveRequests="200"
  keepAliveTimeout="15000"
  maxHttpRequestHeaderSize="16384"
  compression="on"
  compressionMinSize="1024"
  compressableMimeType="text/html,text/xml,text/plain,text/css,application/json,application/javascript,application/xml"
/>

운영 중에는 APM(예: Micrometer/Prometheus/Grafana)로 활성 스레드/큐/GC/응답시간을 반드시 모니터링하세요.


10) 문제 → 원인 → 해결(빠른 표)

문제원인해결
504/499 증가 프록시/백엔드 타임아웃 불일치 Nginx proxy_read_timeout vs Tomcat keepAliveTimeout 재정렬, 백엔드 지연 원인 분석
FD/스레드 고갈 Keep-Alive 과다, maxThreads 과도 maxKeepAliveRequests·keepAliveTimeout 보수화, 스레드 적정화
413/요청 거절 프록시/커넥터 바디 제한 Nginx client_max_body_size↑, Tomcat maxSwallowSize 조정
대역폭 낭비 압축 비활성/오설정 Gzip 활성 + 최소 크기/타입 튜닝(이미지·zip 제외)

11) 체크리스트(배포 전/후)

  • 커넥터 address=127.0.0.1, 8080 외부 차단(UFW/NACL)
  • 스레드/큐: maxThreads, acceptCount, minSpareThreads 합리값
  • 타임아웃: 프록시와 표로 정리해 일치 유지
  • 압축: compressionMinSize·MimeType 점검(또는 Nginx에서 gzip)
  • 보안 헤더: Nginx 스니펫 적용 또는 필터
  • 액세스 로그 패턴에 %D 포함(지연 추적 필수)

FAQ

Q1. HTTP/2를 Tomcat에서 직접 켤까요?
A. 프록시(Nginx)가 앞단이면 Nginx가 HTTP/2를 처리하고 Tomcat은 HTTP/1.1로 두는 구성이 단순하고 안정적입니다. Tomcat에서 HTTP/2를 직접 쓰려면 TLS/ALPN 등 추가 고려가 필요합니다.

Q2. 압축은 Tomcat vs Nginx 어디에서?
A. 보통은 Nginx에서 gzip으로 끝내는 편이 운영이 쉽고 CPU 효율도 좋습니다. 다만 내부망·프록시 없이 Tomcat 단독이면 커넥터 수준 압축을 켜세요(이중 압축 금지).

Q3. 스레드를 올릴수록 성능이 계속 좋아지나요?
A. 아닙니다. CPU·GC·컨텍스트 스위칭 오버헤드가 커집니다. APM 지표를 보며 적정 지점을 찾는 것이 핵심입니다.

이 글의 설정을 적용하면 “설치만 된 톰캣”이 아니라 트래픽을 안정적으로 받는 톰캣으로 올라옵니다. 운영 중 수치(응답 p95/p99, 활성 스레드, 5xx/499 비율)를 보고 한 번에 크게 바꾸기보다 작게-잦게 조정하는 습관이 가장 강력한 튜닝입니다.