본문으로 건너뛰기
Advertisement

정적 파일 분리 서빙

Nginx가 정적 파일을 직접 처리하고 동적 요청만 Tomcat으로 전달하면, Tomcat의 스레드 낭비를 막고 전체 처리량을 크게 향상시킬 수 있습니다. 이 챕터에서는 확장자 기반 분기, 디렉터리 기반 분기, 그리고 실무에서 유의해야 할 파일 경로 관리 방법을 다룹니다.


왜 정적 파일을 분리해야 하는가

Tomcat이 정적 파일을 처리하는 경우:
- /static/logo.png 요청 → Tomcat 스레드 1개 소비 (Java I/O)
- 동시 100개 정적 파일 요청 → 100개 스레드 소비 → 동적 요청 처리 지연

Nginx가 정적 파일을 처리하는 경우:
- /static/logo.png 요청 → Nginx이벤트 루프 처리 (OS sendfile)
- Tomcat 스레드는 오직 비즈니스 로직에만 사용
항목Nginx 직접 서빙Tomcat 서빙
처리량초당 수만 건초당 수백~수천 건
메모리이벤트 기반, 적음스레드당 메모리 필요
CPUOS sendfile 활용JVM 오버헤드
Cache-Control설정 용이코드/설정 필요

방법 1: 확장자 기반 분기

upstream tomcat_backend {
server 127.0.0.1:8080;
keepalive 32;
}

server {
listen 443 ssl;
server_name example.com;
root /var/www/myapp;

# 정적 확장자 — Nginx 직접 서빙
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|bmp|tiff)$ {
expires 30d;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding";
access_log off; # 정적 파일 로그 제외
}

location ~* \.(css|js|mjs)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}

location ~* \.(woff|woff2|ttf|eot|otf)$ {
expires 1y;
add_header Cache-Control "public";
add_header Access-Control-Allow-Origin "*"; # 폰트 CORS
access_log off;
}

location ~* \.(pdf|zip|tar|gz)$ {
expires 1d;
add_header Cache-Control "public";
}

# 나머지 요청 — Tomcat으로 프록시
location / {
proxy_pass http://tomcat_backend;
proxy_http_version 1.1;
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_set_header Connection "";
}
}

방법 2: 디렉터리 기반 분기

Spring Boot나 전통적인 Java EE 앱에서 정적 파일을 /static/, /resources/, /public/ 같은 특정 경로에 두는 경우입니다.

server {
listen 443 ssl;
server_name example.com;
root /opt/tomcat/latest/webapps/ROOT;

# /static/ 경로 — Nginx 직접 서빙
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;

# 파일 없으면 404 (Tomcat으로 넘기지 않음)
try_files $uri =404;
}

# /uploads/ — 사용자 업로드 파일
location /uploads/ {
alias /var/www/uploads/;
expires 7d;
add_header Cache-Control "public";

# 보안: 실행 파일 차단
location ~* \.(php|jsp|py|rb|sh|exe)$ {
deny all;
}
}

# /favicon.ico, /robots.txt
location = /favicon.ico {
log_not_found off;
access_log off;
expires 1y;
}

location = /robots.txt {
log_not_found off;
access_log off;
}

# 나머지 — Tomcat
location / {
proxy_pass http://127.0.0.1:8080;
include /etc/nginx/snippets/proxy-params.conf;
}
}

방법 3: Tomcat webapps 외부 경로 서빙

운영 환경에서 정적 파일을 Tomcat의 webapps/ 외부에 별도로 관리하는 패턴입니다.

# 정적 파일은 /var/www/static/에 별도 배포
# (WAR 배포와 별개로 CDN 동기화 또는 별도 배포 파이프라인)

server {
listen 443 ssl;
server_name example.com;

# 정적 파일 전용 경로
location /assets/ {
alias /var/www/static/;
expires 1y;
add_header Cache-Control "public, immutable";
gzip_static on; # .gz 파일 있으면 자동 서빙
}

# API
location /api/ {
proxy_pass http://127.0.0.1:8080/api/;
include /etc/nginx/snippets/proxy-params.conf;
}

# 나머지 (SPA 또는 SSR)
location / {
proxy_pass http://127.0.0.1:8080;
include /etc/nginx/snippets/proxy-params.conf;
}
}

Spring Boot 정적 파일 경로 이해

Spring Boot의 기본 정적 파일 경로:

src/main/resources/
├── static/ → /static/* URL로 접근
│ ├── css/
│ ├── js/
│ └── images/
├── public/ → /* URL로 접근
└── templates/ → Thymeleaf 등 서버 템플릿

빌드된 JAR/WAR에서의 경로:

myapp.war 내부:
└── WEB-INF/
└── classes/
└── static/ ← 여기에 포함됨

Tomcat 배포 시:

/opt/tomcat/webapps/ROOT/
└── WEB-INF/
└── classes/
└── static/css/app.css ← 경로: /WEB-INF/classes/static/css/app.css

Spring Boot는 ResourceHandlerRegistry/static/** → classpath:/static/을 매핑하므로, URL /static/css/app.css로 접근 가능합니다.


gzip 압축과 정적 파일 최적화

# 전역 gzip 설정 (nginx.conf http 블록)
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml
font/woff
font/woff2;

# 사전 압축된 .gz 파일 서빙 (성능 최우선)
location ~* \.(css|js)$ {
gzip_static on; # /app.js.gz 가 있으면 그걸 서빙
expires 1y;
add_header Cache-Control "public, immutable";
}

파일 경로 관리 — 실전 배포 구조

# 배포 스크립트 예시: 정적 파일 분리 배포

# 1. 정적 파일 추출 (WAR에서)
unzip -j myapp.war "static/*" -d /var/www/static/

# 2. 버전 디렉터리로 관리
VERSION=2024-03-24-001
mkdir -p /var/www/static/$VERSION
unzip -j myapp.war "static/*" -d /var/www/static/$VERSION/

# 3. 심볼릭 링크로 현재 버전 가리키기
ln -sfn /var/www/static/$VERSION /var/www/static/current

# 4. Nginx에서 심볼릭 링크 경로 사용
# location /static/ {
# alias /var/www/static/current/;
# }

실전 체크리스트

# 1. 정적 파일이 Nginx에서 처리되는지 확인
curl -I https://example.com/static/css/app.css
# Server: nginx (Tomcat이 아닌 nginx여야 함)

# 2. 캐시 헤더 확인
# Cache-Control: public, immutable
# Expires: (1년 후 날짜)

# 3. Tomcat 액세스 로그에서 정적 파일 요청이 없는지 확인
grep "\.css\|\.js\|\.png" /opt/tomcat/latest/logs/localhost_access_log*.txt
# (결과 없어야 함)

# 4. Nginx 응답 시간 비교
ab -n 1000 -c 100 https://example.com/static/css/app.css # 빠름
ab -n 1000 -c 100 https://example.com/api/users # 상대적으로 느림

Summary

분기 방식사용 상황
확장자 기반 (`~* .(jpgcss
디렉터리 기반 (location /static/)정적 파일 경로가 명확히 분리됐을 때
외부 경로 (alias)정적 파일을 WAR 외부에서 별도 관리할 때
gzip_static빌드 시 압축 파일 생성 + Nginx 서빙
캐시 전략CSS/JS: immutable 1y, 이미지: 30d, HTML: no-cache
Advertisement