"마케팅 팀에서 이벤트를 진행하자마자 서버가 마비되었습니다. CPU 점유율은 낮은데 동적 API 서버가 응답을 안 해요!"
원인을 분석해보니, 이벤트 페이지에 삽입된 대용량 고화질 이미지 파일들을 사용자들이 다운로드하면서 문제가 생겼습니다. 동적 자바 비즈니스 로직을 수행해야 할 WAS의 스레드 풀 200개가 이미지 파일을 디스크에서 읽어 보내는 '정적 전송 대기 상태'로 모두 잠겨버린 것입니다.
만약 앞단에 가볍고 분주하게 움직이는 Web Server(Nginx)를 두고 이미지 전송을 전담시킨 뒤, 진짜 동적 연산(API)만 WAS(Tomcat)로 흘려보내는 Web-WAS 2-Tier 구조를 구축했다면, 단 한 대의 서버로도 마비 없이 수천 명의 트래픽을 소화했을 것입니다.
- 1웹 서버(Nginx)와 웹 애플리케이션 서버(Tomcat)의 구조적 차이 비교
- 2이벤트 기반(Event-driven)과 멀티스레드 스레드 풀(Thread Pool) 동작 모델의 이해
- 3리버스 프록시(Reverse Proxy) 연동 필요성과 설정 방법
- 4Nginx 가상 호스트 및 Tomcat 연동용 proxy_pass 실습
- 5실무 필수 헤더: 클라이언트 실제 IP(X-Real-IP) 전송 설정
- 6502 Bad Gateway와 504 Gateway Timeout 장애 패턴 및 분석
sudo apt-get update && sudo apt-get install -y nginxsudo systemctl status nginxsudo nginx -tWeb-WAS의 핵심 분리 철학
안내원 Nginx와 요리사 Tomcat — 왜 역할을 나눠야 하는가
WAS(Tomcat)만 단독으로 운영하면 처음엔 잘 돌아가는 것처럼 보입니다. 그런데 이벤트 트래픽이 몰리거나 대용량 파일 요청이 겹치는 순간, 스레드 풀 200개가 이미지 전송 대기에 모두 묶여 정작 핵심인 API 처리가 멈춰버립니다. 이 문제를 구조적으로 해결하는 방법이 Web-WAS 분리입니다. 정적 파일은 Nginx가 초고속으로 처리하고, Tomcat은 오직 비즈니스 로직에만 집중하게 만드는 것이 핵심입니다.

Nginx (Web Server) = 카페 접수원
Nginx는 이벤트 루프(Event Loop)를 사용하는 비동기 구조입니다. CPU 코어당 1개의 Worker 프로세스만으로 수만 건의 정적 요청을 동시에 처리할 수 있습니다. 파일 I/O가 발생해도 스레드를 블로킹하지 않고 이벤트 큐에서 관리하기 때문에 메모리와 CPU 소모가 극단적으로 적습니다.
Tomcat (WAS) = 전문 요리사
Tomcat은 요청 1개당 JVM 스레드 1개를 완전히 점유하는 Thread-per-request 구조입니다. 스레드 풀이 최대 200개로 설정된 상태에서 200개의 느린 요청이 유입되면, 201번째 요청은 스레드가 릴리즈될 때까지 대기하다 타임아웃됩니다.
[인터넷 요청]
│
▼
┌────────────────────────────────┐
│ Nginx Web Server (Port 80) │ ← HTML, CSS, Image 직접 처리
└────────────────────────────────┘
│ proxy_pass (동적 요청만)
▼
┌────────────────────────────────┐
│ Tomcat WAS (Port 8080) │ ← 비즈니스 로직, DB 접근 집중
└────────────────────────────────┘

Nginx 리버스 프록시 연동 실습
기존 기본 설정을 비활성화하고, 프록시 헤더를 정확히 전달하는 전용 서버 블록을 작성합니다.
# 기본 설정 비활성화
sudo rm -f /etc/nginx/sites-enabled/default
# 새로운 전용 설정 파일 생성
sudo tee /etc/nginx/sites-available/infra-platform.conf > /dev/null <<'EOF'
server {
listen 80;
server_name localhost;
# 정적 리소스는 Nginx가 직접 처리
location /static/ {
alias /var/www/infra-platform/static/;
expires 30d;
access_log off;
}
# 동적 API 요청은 WAS(Tomcat)로 중계
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
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;
proxy_connect_timeout 10s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}
EOF
sudo rm -f /etc/nginx/sites-enabled/default && sudo tee /etc/nginx/sites-available/infra-platform.conf > /dev/null/etc/nginx/sites-available/infra-platform.conf파일이 생성됐는가- 파일 내용에
proxy_pass http://127.0.0.1:8080;라인이 있는가 proxy_set_header X-Real-IP $remote_addr;헤더 설정이 포함됐는가
심볼릭 링크로 설정을 활성화하고 문법 오류가 없는지 검사한 뒤 리로드합니다.
# 심볼릭 링크 생성
sudo ln -sf /etc/nginx/sites-available/infra-platform.conf /etc/nginx/sites-enabled/
# 구문 검사
sudo nginx -t
# 성공 출력: nginx: configuration file /etc/nginx/nginx.conf test is successful
# 무중단 설정 리로드
sudo systemctl reload nginx
sudo ln -sf /etc/nginx/sites-available/infra-platform.conf /etc/nginx/sites-enabled/ && sudo nginx -t리버스 프록시가 정상 동작하는지 두 가지 방법으로 확인합니다.
# 1. HTTP 응답 헤더 확인 — Server: nginx 가 보이면 정상
curl -I http://localhost
# 2. WAS 로그에 X-Real-IP 헤더가 전달되는지 확인
# Tomcat access log에 %{X-Forwarded-For}i 패턴이 있어야 실제 IP 기록됨
sudo tail -20 /opt/tomcat/logs/localhost_access_log.*.txt
Server: nginx헤더가 응답에 보이면 Nginx가 앞단에서 동작 중- WAS 로그에
127.0.0.1대신 실제 클라이언트 IP가 찍히면 X-Real-IP 전달 성공 502 Bad Gateway가 나오면 8080 포트에 WAS가 기동 중인지 확인 필요
실제 업무에서 이 지식이 쓰이는 상황:
주니어 인프라 엔지니어로 입사하면 시니어가 툭 던지는 두 가지 지시가 있습니다.
"Nginx max_connections랑 Worker 프로세스 설정 좀 봐줘"
최대 동시 접속 수는 worker_processes × worker_connections입니다. lscpu로 CPU 코어 수를 확인하고 worker_processes auto로 설정합니다. ulimit -n(파일 디스크립터 한계)보다 worker_connections가 크면 기동 시 에러가 나므로 반드시 확인합니다.
"WAS 실주소 로깅 똑바로 되고 있는지 확인해봐"
Nginx에 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for가 들어있는지 확인합니다. Tomcat의 server.xml AccessLogValve 포맷 패턴에 %{X-Forwarded-For}i가 지정되어 있어야 클라이언트 실제 IP가 로그에 남습니다. 테스트 요청을 직접 쏴서 로그에 실제 IP가 찍히는지 증빙합니다.
다음 모듈에서는 Web/WAS/DB를 실제 네트워크 망으로 분리하는 3-Tier 이중화 구조를 다룹니다.