이 글은 1편의 이론을 바탕으로 프로덕션에서 곧바로 쓸 수 있는 Nginx 설정 템플릿과 설계 의도를 정리합니다.
단순히 “되는 설정”이 아니라, 왜 이렇게 설정하는지를 각 블록마다 설명합니다. 문서 끝에는 점검용 커맨드와 운영 팁을 덧붙였습니다.
디렉터리 구성
/etc/nginx/
├─ nginx.conf # 글로벌 설정
├─ conf.d/
│ ├─ 00-security.conf # 공통 보안/압축/캐시/업스트림 헬퍼
│ ├─ api.conf # API + SSE 프록시
│ └─ static.conf # 정적 파일(선택)
└─ snippets/
├─ ssl.conf # TLS/HTTP2/HSTS 공통
└─ proxy-headers.conf # 공통 프록시 헤더
Docker Compose를 쓴다면 로컬에는 ./nginx/ 아래 동일한 구조로 두고 볼륨 마운트로 컨테이너 안 /etc/nginx/에 반영합니다.
1. nginx.conf — 코어/워커/로그 기본값
user nginx;
worker_processes auto; # CPU 코어 수에 맞추어 자동
pid /var/run/nginx.pid;
events {
worker_connections 4096; # 동시 접속에 여유
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 성능 관련 기본값
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
# 접근/에러 로그
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct=$upstream_connect_time '
'urt=$upstream_response_time uaddr=$upstream_addr';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
# 공통 보안/압축/캐시/업스트림 헬퍼
include /etc/nginx/conf.d/*.conf;
}
왜 이렇게 했는가
- worker_processes auto 는 코어 수에 맞춰 병렬 처리량을 극대화합니다.
- log_format 에 upstream 지표(연결/응답 시간)를 포함해 Spring/DB 병목을 바로 파악합니다.
- keepalive_timeout 65 는 ALB/프록시와의 idle 타임아웃과 충돌을 줄입니다.
2. security.conf — 공통 보안/압축/캐시
# 공통 보안 헤더(필요에 따라 강화)
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header Referrer-Policy no-referrer-when-downgrade always;
add_header X-XSS-Protection "0" always;
# HSTS(HTTPS만 운용 시에만 활성화)
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# 압축: 텍스트 자원만
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript application/xml text/xml;
# 파일 업로드 한도(대용량 업로드 시)
client_max_body_size 20m;
# 업스트림 공통 헤더
map $http_x_forwarded_proto $real_proto {
default $scheme;
https https;
}
# 프록시 공통 헤더 스니펫
# (snippets/proxy-headers.conf 로 분리해도 좋습니다)
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 $real_proto;
proxy_http_version 1.1;
proxy_read_timeout 75s;
proxy_send_timeout 75s;
왜 이렇게 했는가
- 보안 헤더는 애플리케이션 이전 단계에서 최소한의 방어막을 제공합니다.
- gzip 은 텍스트 응답만 압축하여 CPU/지연의 밸런스를 맞춥니다.
- client_max_body_size 는 파일 업로드 실패를 예방합니다.
- X-Forwarded-* 헤더는 애플리케이션에서 정확한 클라이언트 정보를 알게 해 줍니다.
3. ssl.conf — TLS/HTTP2/HSTS 스니펫
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
# 인증서 경로(실서버 도메인 반영)
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# HTTP/2 권장
# server {} 블록 listen에 'http2' 추가
왜 이렇게 했는가
- TLS 1.2/1.3만 허용해 구식 암호화 스위트를 차단합니다.
- 인증서 경로는 Certbot이 갱신하는 위치로 고정합니다.
4. HTTP(80): ACME 챌린지 + HTTPS 리디렉션
server {
listen 80;
server_name example.com www.example.com;
# Certbot ACME 인증 파일 제공
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# 나머지는 HTTPS로 리디렉션
location / {
return 301 https://$host$request_uri;
}
}
왜 이렇게 했는가
- 80 포트는 도메인 소유 검증(ACME) 과 HTTPS 리디렉션 전용으로 심플하게 둡니다.
5. HTTPS(443): API + SSE 프록시(핵심)
server {
listen 443 ssl http2;
server_name example.com;
include /etc/nginx/snippets/ssl.conf;
# 공통 보안/압축/프록시 설정
include /etc/nginx/conf.d/00-security.conf;
# ---- 일반 API ----
location / {
proxy_pass http://127.0.0.1:8080; # Spring Boot
# 공통 헤더/타임아웃은 00-security.conf에서 지정
# 캐시가 필요 없는 API라면 no-store 권장
add_header Cache-Control "no-store" always;
}
# ---- SSE 엔드포인트 ----
# 예: GET /api/tests/{id}/events
location ~* ^/api/tests/.*/events$ {
proxy_pass http://127.0.0.1:8080;
proxy_buffering off; # 버퍼링 비활성화 → 즉시 전송
proxy_cache off; # 캐시 금지
proxy_read_timeout 3600s; # 장기 연결 유지
add_header X-Accel-Buffering "no"; # 일부 리버스 프록시에서 버퍼링 방지
# 필요 시 CORS 노출
# add_header Access-Control-Allow-Origin *;
}
# ---- 헬스 체크/액추에이터(선택) ----
location /actuator/health {
proxy_pass http://127.0.0.1:8080;
add_header Cache-Control "no-store" always;
}
# ---- 정적 파일(선택, 프론트 빌드 산출물) ----
# location /app/ {
# alias /var/www/html/;
# try_files $uri $uri/ /app/index.html;
# }
}
왜 이렇게 했는가
- SSE는 ‘지연 없이’ 흘려야 하므로 proxy_buffering off + X-Accel-Buffering: no 를 같이 둡니다.
- 장시간 스트림을 고려해 proxy_read_timeout 을 충분히 크게 둡니다(예: 30~60분).
- API는 Cache-Control: no-store 로 브라우저/중간 캐시를 차단합니다(민감 응답).
6. 개발용(HTTP only): Docker Compose에 맞는 최소 예시
server {
listen 80;
server_name localhost;
# 일반 API 프록시
location / {
proxy_pass http://app:8080; # docker-compose 서비스명
include /etc/nginx/conf.d/00-security.conf;
add_header Cache-Control "no-store" always;
}
# SSE 전용: 즉시 전송 + 장기 연결
location ~* ^/api/tests/.*/events$ {
proxy_pass http://app:8080;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
add_header X-Accel-Buffering "no";
}
}
왜 이렇게 했는가
- 로컬에서는 HTTPS(443) 없이 빠르게 개발/디버깅,
운영만 443 + 인증서로 보안/성능을 확보합니다.
7. 설계 의도 정리
- TLS 종료를 Nginx에서
앱은 평문 HTTP로 빠르게 비즈니스 로직에 집중합니다. 인증서/암복호화 관리는 게이트웨이 한 곳에 모읍니다. - 공통 보안/압축/프록시 설정의 분리
00-security.conf 같은 공통 스니펫 분리는 설정 누락·불일치 사고를 줄입니다. - SSE 버퍼링 완전 차단
proxy_buffering off 와 X-Accel-Buffering: no 를 함께 넣어 즉시 전송을 보장합니다. 중간 버퍼링으로 TTFB가 지연되면 UX가 급격히 나빠집니다. - 장기 연결 타임아웃 상향
proxy_read_timeout 을 넉넉히 잡아 ALB/Nginx idle과 충돌을 줄입니다. 동시에 서버 측 주기 핑(예: 20초) 을 보내면 더 안정적입니다. - 로그에 upstream 타이밍 포함
애플리케이션/DB 지연과 네트워크 지연을 구분할 수 있어 장애 분석 시간이 단축됩니다. - Cache-Control 최소화 전략
민감 데이터(API)는 no-store 로 캐싱을 막고, 정적 파일(해시 빌드 산출물)은 immutable 로 강하게 캐싱하는 “양극화 전략”이 운영 비용을 줄입니다. - 업스트림 keepalive
백엔드 서버 수가 늘수록 커넥션 생성 비용이 커집니다. keepalive는 이를 상쇄해 지연/CPU를 절감합니다. - 구성 파일 역할 분리
운영에서 변경이 잦은 것(도메인, 인증서 경로, 라우팅)과 드문 것(코어/압축 정책)을 분리하면 릴리즈 리스크가 줄고 코드 리뷰 난이도가 낮아집니다.
흔한 문제와 빠른 체크
- SSE가 중간에 끊긴다 → proxy_buffering off, proxy_read_timeout 확인. 브라우저/프록시 idle도 확인.
- 클라이언트 IP가 모두 127.0.0.1로 찍힌다 → X-Real-IP, X-Forwarded-For 헤더 전달과 애플리케이션의 추출 로직 확인.
- 대용량 업로드가 실패한다 → client_max_body_size 조정.
- 인증서 만료 → certbot renew cron/systemd timer 설정, 만료 알림 모니터링.
운영 체크리스트
- 80 → ACME + HTTPS 리디렉션만
- 443 → ssl, http2 + 보안 헤더 + 압축
- API는 no-store, 정적 자원은 immutable
- SSE는 proxy_buffering off + X-Accel-Buffering: no + 큰 proxy_read_timeout
- upstream keepalive, 실패 감지 값 조정
- 액세스 로그에 upstream 타이밍 포함
- nginx -t → nginx -s reload 플로우 준수
- 인증서 자동 갱신 및 만료 모니터링
마무리
이 구성은 Spring Boot + Nginx 게이트웨이에서 HTTPS, 리버스 프록시, SSE 스트리밍을 안정적으로 운영하기 위한 현실적인 기본 템플릿입니다. 핵심은 “즉시 전송이 필요한 경로(SSE) 와 그 외 경로(API/정적) 를 명확히 분리하고, 공통 정책은 스니펫으로 일관되게 적용”하는 것입니다.
이대로 시작한 뒤, 서비스의 트래픽 패턴과 모니터링 지표에 맞춰 timeouts/버퍼/캐시/로그를 점진 튜닝하면 됩니다.
'프로젝트 > 웹 성능 테스트' 카테고리의 다른 글
| 롱폴링 비동기 완료 (0) | 2025.11.27 |
|---|---|
| 비동기 통신 방식 비교와 A/B 테스트 설계 (0) | 2025.11.09 |
| Nginx 도입 1편: 왜 Nginx (0) | 2025.11.06 |
| Server Sent Events(SSE): 구현 (0) | 2025.11.02 |
| Server Sent Events(SSE): 설계 (0) | 2025.11.02 |