server.xml 은 아래 계층으로 중첩됩니다. 괄호 없이 텍스트로 정리하면 이렇습니다.
Server (port=종료 포트)
GlobalNamingResources
Service
Connector (HTTP, AJP 등 포트별로 여러 개)
Engine (defaultHost 지정)
Host (name=가상 호스트, appBase=배포 폴더)
Valve (AccessLogValve, RemoteIpValve 등)
Context (앱 하나, 보통 별도 context.xml 로 분리)Server 아래 Service 는 보통 하나(Catalina)이고, 그 안에 Connector 여러 개와 Engine 하나가 들어갑니다. Host 는 도메인별 가상 호스트이고 그 안의 Valve 가 요청을 가로채 로그를 남기거나 헤더를 고칩니다.
Server 요소의 port 속성이 바로 종료(shutdown) 포트입니다. 이 포트로 SHUTDOWN 문자열을 보내면 Tomcat 이 완전 종료됩니다. 기본값 8005 는 다른 인스턴스와 겹치기 쉬워 설치마다 바꿔야 합니다.
| 속성 | 기본값 | 의미 |
|---|---|---|
port |
8005 | 종료 명령을 받는 포트 |
shutdown |
SHUTDOWN | 종료를 트리거하는 문자열 |
종료 포트를 외부에 열어 두면 문자열만 알면 누구나 Tomcat 을 내릴 수 있습니다. 04 레슨에서 이 포트를 -1 로 막는 방법을 다룹니다.
Connector 는 포트마다 하나씩 선언하며 프로토콜(HTTP/1.1, AJP/1.3)을 속성으로 지정합니다. 기본 8080(HTTP), 8009(AJP), 8443(HTTPS, 주석 처리) 세 개가 기본 제공됩니다. 같은 서버에 다른 앱이 8080 을 쓰면 바로 기동 실패로 이어집니다.
인스턴스를 여러 개 두는 경우(03 레슨) 포트가 인스턴스마다 겹치지 않아야 합니다. 포트를 정할 때는 종료 포트, HTTP 포트, 리다이렉트 포트를 함께 관리 대장에 적어 두는 편이 안전합니다.
maxThreads 는 동시에 요청을 처리하는 최대 스레드 수이고, acceptCount 는 스레드가 모두 찼을 때 대기시킬 수 있는 연결 수입니다. 두 값을 넘는 요청은 즉시 거절되거나 타임아웃됩니다.
| 속성 | 기본값 | 의미 |
|---|---|---|
maxThreads |
200 | 동시 처리 최대 스레드 수 |
acceptCount |
100 | 대기열 최대 연결 수 |
connectionTimeout |
60000 | 연결 유지 제한시간(ms) |
값을 무작정 키우면 스레드 경쟁과 메모리 사용만 늘어납니다. 예상 동시접속과 WAS 뒤 DB 커넥션 풀 크기를 함께 보고 정합니다.
URIEncoding 은 URL 경로·쿼리를 디코딩할 문자셋이고, 지정하지 않으면 버전에 따라 다를 수 있어 명시하는 편이 안전합니다. maxPostSize 는 폼 데이터 본문 크기, maxSwallowSize 는 오류 응답을 보내기 전 남은 요청 본문을 읽어 버리는 한도입니다.
| 속성 | 기본값 | 의미 |
|---|---|---|
URIEncoding |
버전마다 다름 | URL 디코딩 문자셋 |
maxPostSize |
2097152 | POST 본문 최대 크기(byte) |
maxSwallowSize |
2097152 | 오류 시 요청 흡수 한도(byte) |
compression |
off | gzip 압축 여부 |
URIEncoding="UTF-8" 을 빠뜨리면 한글 파라미터가 깨지는 문제가 자주 나옵니다. 업로드 앱은 maxPostSize 를 파일 크기 정책에 맞춰 올려야 합니다.
AJP(Apache JServ Protocol)는 아파치 웹서버 연동용 프로토콜입니다. 2020년 CVE-2020-1938(일명 Ghostcat)로 AJP 포트가 노출되면 파일을 읽히는 취약점이 알려졌습니다. nginx 는 AJP 가 아닌 HTTP 로 프록시하므로 이 커넥터는 대부분 필요 없습니다.
쓰지 않는 포트는 지우는 편이 가장 안전합니다. 꼭 필요하면 secretRequired="true" 와 address="127.0.0.1" 로 로컬 전용으로 좁혀야 합니다.
Engine 은 들어온 요청을 어느 Host 로 보낼지 결정하는 상위 컨테이너입니다. defaultHost 는 요청의 Host 헤더가 어떤 Host 이름과도 안 맞을 때 쓸 기본값입니다. 보통 Host 는 localhost 하나만 두고 도메인 여러 개는 Alias 로 묶습니다.
appBase 는 war·앱 폴더를 두는 위치이고, 기본값 webapps 는 Tomcat 설치 폴더 기준 상대경로입니다. 버전 업그레이드 시 폴더가 바뀌는 01 레슨 구조와 맞추려면 $BASE/webapps 같은 절대경로로 분리해야 앱이 그대로 남습니다.
| 속성 | 의미 |
|---|---|
appBase |
war·앱 폴더 위치 |
autoDeploy |
실행 중 새 war 자동 배포 |
unpackWARs |
war 를 풀어서 서비스 |
운영에서는 autoDeploy 를 켜 두면 실수로 넣은 war 가 바로 배포될 수 있어, 배포 절차(리눅스 운영 05 참조)로 통제하는 편이 안전합니다.
AccessLogValve 는 Host 안에 넣는 Valve 로, 요청마다 한 줄씩 로그를 남깁니다. directory 는 로그 폴더, pattern 은 로그 형식이며 %D 를 넣으면 응답 처리 시간(ms)이 같이 찍힙니다.
| pattern 기호 | 의미 |
|---|---|
%h |
접속 클라이언트 주소 |
%t |
요청 시각 |
%r |
요청 줄(메서드·경로) |
%s |
응답 상태 코드 |
%b |
응답 바이트 수 |
%D |
응답 처리 시간(ms) |
%D 로 느린 요청을 로그만 보고 걸러낼 수 있어 장애 조사 첫 단계로 자주 씁니다.
nginx 뒤에 Tomcat 을 두면 접근 로그의 %h 가 전부 nginx 서버 주소(127.0.0.1)로 찍힙니다. RemoteIpValve 는 X-Forwarded-For, X-Forwarded-Proto 헤더를 읽어 실제 클라이언트 IP 와 원래 프로토콜(http/https)을 복원합니다.
| 속성 | 의미 |
|---|---|
remoteIpHeader |
실제 클라이언트 IP 헤더 이름 |
protocolHeader |
원래 프로토콜 헤더 이름 |
internalProxies |
신뢰할 프록시 IP 대역(정규식) |
internalProxies 를 nginx 가 있는 서버 대역으로만 좁혀야 합니다. 너무 넓게 잡으면 외부 요청자가 헤더를 위조해 IP 를 속일 수 있습니다.
server.xml 은 핫 리로드 대상이 아닙니다. Connector, Host, Valve 를 고쳤으면 Tomcat 을 재시작해야 반영됩니다. war 배포·nginx 전환 같은 무중단 절차는 리눅스 운영 05 를 참조합니다.
sed 로 태그 하나만 잘못 지워도 Tomcat 은 파싱 오류로 기동을 거부합니다. python3 -c 'import xml.dom.minidom as m; m.parse(f)' 로 문법만 빠르게 확인하고, 실제 서버에서는 catalina.sh configtest 계열 점검이나 시험 기동으로 한 번 더 확인합니다.