Nginx 리버스 프록시 설정은 현대 웹 아키텍처의 핵심 기술입니다. 전 세계 상위 1만 개 웹사이트의 절반 이상이 Nginx를 사용하며, 넷플릭스·깃허브·에어비앤비 같은 대형 서비스들도 Nginx를 프론트 레이어로 사용합니다. 단순히 “설치하면 된다”는 수준을 넘어서, location 블록 매칭 우선순위, upstream 로드밸런싱 알고리즘 선택 기준, 캐시 키 설계, Rate Limiting 전략까지 — 이 내용들을 정확히 알아야 프로덕션 환경에서 Nginx를 제대로 다룰 수 있습니다. 이 글에서는 Nginx의 설정 파일 구조부터 실무에서 바로 쓸 수 있는 완성형 설정까지 단계별로 정리합니다.
목차
- Nginx 기초 – 동작 원리와 설정 파일 구조
- 리버스 프록시 – proxy_pass와 location 블록 완전 정복
- 로드밸런싱 – upstream 알고리즘과 헬스체크 설정
- 캐시 – proxy_cache로 응답 속도 극대화
- 보안·성능 최적화 – SSL·gzip·Rate Limiting
- 실전 완성형 설정 – 프로덕션 Nginx 템플릿
1. Nginx 기초 – 동작 원리와 설정 파일 구조
Nginx 리버스 프록시 설정을 다루기 전에 Nginx가 어떤 구조로 동작하는지, 설정 파일이 어떻게 구성되는지 정확히 이해해야 합니다. 설정의 오동작 대부분은 구조를 모른 채 복붙하는 데서 발생합니다.
Nginx의 이벤트 기반 아키텍처
Nginx와 Apache의 가장 큰 차이는 동시 연결 처리 방식입니다.
Apache (Thread-per-Request 모델):
요청 1 → 스레드 1 생성 → 처리 완료 → 스레드 종료
요청 2 → 스레드 2 생성 → 처리 완료 → 스레드 종료
요청 N → 스레드 N 생성 → 처리 완료 → 스레드 종료
동시 접속 10,000 → 스레드 10,000개 → 메모리 폭발
Nginx (Event-Driven 비동기 모델):
Master Process 1개
└── Worker Process N개 (보통 CPU 코어 수)
└── 각 Worker가 이벤트 루프로 수천 개 연결 처리
동시 접속 10,000 → Worker 수개 → 메모리 안정
이 구조 덕분에 Nginx는 C10K(동시 접속 1만 명) 문제를 효율적으로 처리합니다.
설정 파일 전체 구조
nginx
# /etc/nginx/nginx.conf
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 1. Main 컨텍스트 (전역 설정)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
user nginx;
worker_processes auto; # CPU 코어 수에 맞게 자동 설정
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 2. Events 컨텍스트 (연결 처리 방식)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
events {
worker_connections 1024; # Worker당 최대 동시 연결 수
use epoll; # Linux 최적 I/O 모델
multi_accept on; # 한 번에 여러 연결 수락
}
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 3. HTTP 컨텍스트 (HTTP 처리 전역 설정)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on; # 커널 레벨 파일 전송 최적화
tcp_nopush on; # 패킷 최적화
keepalive_timeout 65; # Keep-Alive 연결 유지 시간(초)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 4. Server 컨텍스트 (가상 호스트 설정)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
server {
listen 80;
server_name example.com;
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 5. Location 컨텍스트 (URL 패턴 매칭)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
location / {
proxy_pass http://backend;
}
}
# 외부 설정 파일 포함 (사이트별 분리 관리)
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
location 블록 매칭 우선순위 – 가장 많이 틀리는 부분
nginx
# location 매칭 순위 (높을수록 우선)
# 1위: = (정확히 일치) - 가장 높은 우선순위
location = /exact {
return 200 "정확히 /exact";
}
# 2위: ^~ (전방 일치, 정규식보다 우선)
location ^~ /static/ {
root /var/www;
# 정규식 location보다 먼저 적용
}
# 3위: ~ (대소문자 구분 정규식)
location ~ \.php$ {
fastcgi_pass php_backend;
}
# 4위: ~* (대소문자 무시 정규식)
location ~* \.(jpg|png|gif)$ {
expires 30d;
}
# 5위: / (접두사 일치 - 기본값, 가장 낮은 우선순위)
location / {
proxy_pass http://backend;
}
# ⚠️ 중요: Nginx는 가장 긴 접두사 매칭을 먼저 찾고,
# 정규식을 순서대로 검사합니다.
# = 와 ^~ 는 매칭 즉시 검색 종료합니다.
2. 리버스 프록시 – proxy_pass와 location 블록 완전 정복
리버스 프록시는 Nginx 리버스 프록시 설정의 핵심입니다. 클라이언트는 Nginx에게만 요청하고, Nginx가 백엔드 서버에 대신 요청한 후 응답을 전달합니다.
proxy_pass 기본 설정과 URL 변환 규칙
proxy_pass에서 가장 많이 발생하는 실수는 슬래시(/) 유무에 따른 URL 변환 차이를 모르는 것입니다.
nginx
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 케이스 1: proxy_pass에 슬래시 없음
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
location /api/ {
proxy_pass http://backend:8080;
# 클라이언트 요청: /api/users
# 백엔드 전달: /api/users (location 경로 그대로 포함)
}
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 케이스 2: proxy_pass에 슬래시 있음
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
location /api/ {
proxy_pass http://backend:8080/;
# 클라이언트 요청: /api/users
# 백엔드 전달: /users (/api/ 부분이 /로 치환됨)
}
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 케이스 3: proxy_pass에 경로 지정
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
location /api/ {
proxy_pass http://backend:8080/v2/;
# 클라이언트 요청: /api/users
# 백엔드 전달: /v2/users (/api/ → /v2/ 치환)
}
필수 프록시 헤더 설정
nginx
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend:8080;
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 필수 헤더 전달 (없으면 백엔드가 잘못된 정보 받음)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
proxy_set_header Host $host;
# 백엔드가 어떤 도메인으로 요청 받았는지 알아야 할 때
proxy_set_header X-Real-IP $remote_addr;
# 클라이언트 실제 IP (없으면 백엔드에서 Nginx IP만 보임)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 프록시 체인 전체 IP 목록
proxy_set_header X-Forwarded-Proto $scheme;
# 원본 프로토콜 (http/https) 전달
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 타임아웃 설정
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
proxy_connect_timeout 10s; # 백엔드 연결 대기 시간
proxy_send_timeout 60s; # 백엔드에 요청 전송 대기
proxy_read_timeout 60s; # 백엔드 응답 대기 시간
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 버퍼링 설정 (백엔드 응답 임시 저장)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
}
}
헤더 설정 중앙화 – proxy_params 파일 활용
nginx
# /etc/nginx/proxy_params 파일 (공통 설정 분리)
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_send_timeout 60s;
proxy_read_timeout 60s;
# 각 server 블록에서 한 줄로 포함
location / {
proxy_pass http://backend:8080;
include /etc/nginx/proxy_params; # 공통 설정 재사용
}
3. 로드밸런싱 – upstream 알고리즘과 헬스체크 설정
upstream 블록은 Nginx 로드밸런싱의 핵심입니다. 여러 백엔드 서버를 그룹으로 묶고, 요청 분배 알고리즘을 지정합니다.
로드밸런싱 알고리즘 4가지
nginx
http {
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 알고리즘 1: Round Robin (기본값, 지정 없을 때)
# 요청을 순서대로 번갈아 분배
# 적합: 서버 사양이 동일하고 요청 처리 시간이 균일할 때
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
upstream backend_rr {
server was1.internal:8080;
server was2.internal:8080;
server was3.internal:8080;
# 요청: was1 → was2 → was3 → was1 → ...
}
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 알고리즘 2: Weighted Round Robin (가중치 라운드로빈)
# 가중치 비율로 요청 분배
# 적합: 서버 사양이 다를 때 (고사양 서버에 더 많이)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
upstream backend_weighted {
server was1.internal:8080 weight=5; # 5/8 비율
server was2.internal:8080 weight=2; # 2/8 비율
server was3.internal:8080 weight=1; # 1/8 비율
}
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 알고리즘 3: Least Connections (최소 연결)
# 현재 연결이 가장 적은 서버로 분배
# 적합: 요청마다 처리 시간이 크게 다를 때 (가장 실용적)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
upstream backend_least_conn {
least_conn;
server was1.internal:8080;
server was2.internal:8080;
server was3.internal:8080;
}
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 알고리즘 4: IP Hash (세션 고정)
# 동일 클라이언트 IP는 항상 같은 서버로
# 적합: 서버 간 세션 공유 없이 세션 유지 필요할 때
# 단점: 특정 IP에 트래픽 집중 시 불균형 발생
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
upstream backend_ip_hash {
ip_hash;
server was1.internal:8080;
server was2.internal:8080;
server was3.internal:8080;
}
}
upstream 고급 옵션 – 장애 감지와 백업 서버
nginx
upstream backend {
least_conn;
server was1.internal:8080
weight=3
max_fails=3 # 이 횟수만큼 실패 시 일시 제외
fail_timeout=30s; # 30초 동안 제외 후 재시도
server was2.internal:8080
weight=3
max_fails=3
fail_timeout=30s;
server was3.internal:8080
weight=1
max_fails=3
fail_timeout=30s;
server backup.internal:8080 backup;
# backup: 모든 주 서버 장애 시에만 사용
keepalive 32;
# upstream과의 Keep-Alive 연결 유지 수 (성능 향상)
}
server {
location / {
proxy_pass http://backend;
include /etc/nginx/proxy_params;
# 장애 서버 자동 재시도
proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 3; # 최대 재시도 횟수
proxy_next_upstream_timeout 10s; # 재시도 총 허용 시간
}
}
Nginx Plus 없이 헬스체크 구현 (오픈소스 방법)
nginx
# nginx_upstream_check_module 또는 수동 패시브 체크 활용
upstream backend {
server was1.internal:8080 max_fails=2 fail_timeout=10s;
server was2.internal:8080 max_fails=2 fail_timeout=10s;
# max_fails 횟수 실패 시 fail_timeout 동안 제외
# 이것이 오픈소스 Nginx의 패시브 헬스체크
}
# 헬스체크 전용 엔드포인트 설정 (모니터링 시스템과 연동)
server {
listen 8081; # 내부 모니터링 포트
location /nginx_status {
stub_status on; # Nginx 상태 정보 노출
allow 10.0.0.0/8; # 내부망만 허용
deny all;
}
}
4. 캐시 – proxy_cache로 응답 속도 극대화
Nginx 캐시는 백엔드 서버의 응답을 Nginx 레벨에서 저장해, 동일 요청에 대해 백엔드를 거치지 않고 즉시 응답하는 기능입니다. 잘 설정하면 백엔드 부하를 80~90% 줄일 수 있습니다.
캐시 기본 설정 구조
nginx
http {
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Step 1: 캐시 저장 영역 정의 (http 블록에 위치)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
proxy_cache_path /var/cache/nginx # 캐시 파일 저장 경로
levels=1:2 # 디렉토리 계층 구조
keys_zone=api_cache:10m # 캐시 키 저장 메모리 영역 이름:크기
max_size=1g # 캐시 디렉토리 최대 크기
inactive=60m # 이 시간 동안 접근 없으면 삭제
use_temp_path=off; # 임시 경로 없이 바로 저장 (성능↑)
server {
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Step 2: 캐시 적용 (location 블록)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
location /api/ {
proxy_pass http://backend;
proxy_cache api_cache; # 사용할 캐시 영역 지정
proxy_cache_key "$scheme$request_method$host$request_uri";
# 캐시 키: 이 조합이 동일하면 같은 캐시 사용
proxy_cache_valid 200 302 10m; # 200·302 응답은 10분 캐시
proxy_cache_valid 404 1m; # 404 응답은 1분 캐시
proxy_cache_valid any 5m; # 나머지는 5분 캐시
proxy_cache_use_stale error timeout updating
http_500 http_502 http_503;
# 백엔드 장애 시 만료된 캐시라도 응답 (서비스 연속성)
proxy_cache_lock on;
# 동일 캐시 키 요청이 동시에 오면 첫 번째만 백엔드로 전달
# 나머지는 캐시 완성될 때까지 대기 (Cache Stampede 방지)
add_header X-Cache-Status $upstream_cache_status;
# 응답 헤더에 캐시 상태 표시 (HIT/MISS/BYPASS/EXPIRED)
}
}
}
캐시 무효화 – 특정 요청 캐시 제외
nginx
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 로그인한 사용자, POST 요청, 특정 쿠키는 캐시 제외
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
map $request_method $no_cache_method {
POST 1;
PUT 1;
DELETE 1;
PATCH 1;
default 0;
}
map $http_cookie $no_cache_cookie {
~*sessionid 1; # 세션 쿠키 있으면 캐시 제외
~*auth_token 1; # 인증 토큰 있으면 캐시 제외
default 0;
}
server {
location /api/ {
proxy_pass http://backend;
proxy_cache api_cache;
# 위 조건이 하나라도 1이면 캐시 우회
proxy_cache_bypass $no_cache_method $no_cache_cookie;
proxy_no_cache $no_cache_method $no_cache_cookie;
}
}
정적 파일 브라우저 캐시 설정
nginx
# 정적 파일은 브라우저 캐시 적극 활용
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
root /var/www/html;
expires 30d;
add_header Cache-Control "public, immutable";
# immutable: 만료 전까지 재검증 요청 안 함
}
location ~* \.(css|js)$ {
root /var/www/html;
expires 7d;
add_header Cache-Control "public";
}
location ~* \.(woff|woff2|ttf|eot)$ {
root /var/www/html;
expires 365d;
add_header Cache-Control "public, immutable";
add_header Access-Control-Allow-Origin "*"; # 폰트 CORS 허용
}
5. 보안·성능 최적화 – SSL·gzip·Rate Limiting
SSL/TLS 설정 – 현대적 보안 기준
nginx
server {
listen 443 ssl http2; # HTTP/2 활성화
server_name example.com;
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# SSL 인증서 경로
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 현대적 TLS 설정 (TLS 1.2 이상만 허용)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:
ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off; # 클라이언트 선호 암호화 허용
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# SSL 세션 재사용 (TLS 핸드쉐이크 비용 절감)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ssl_session_cache shared:SSL:10m; # 10MB 공유 캐시
ssl_session_timeout 1d;
ssl_session_tickets off; # 보안상 비활성화 권장
# HSTS (브라우저에 HTTPS 강제 지시)
add_header Strict-Transport-Security
"max-age=63072000; includeSubDomains; preload" always;
# OCSP Stapling (인증서 폐기 확인 성능 최적화)
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
}
# HTTP → HTTPS 리다이렉트
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
gzip 압축 설정
nginx
http {
gzip on;
gzip_vary on; # Vary: Accept-Encoding 헤더 추가
gzip_proxied any; # 프록시 응답도 압축
gzip_comp_level 6; # 압축 레벨 (1~9, 6이 속도·압축률 균형)
gzip_min_length 1024; # 1KB 이상만 압축 (작은 파일 압축 의미 없음)
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/xml+rss
image/svg+xml;
# ⚠️ 이미지(jpg/png)는 이미 압축되어 있어 gzip 제외
}
Rate Limiting – DDoS·API 남용 방어
nginx
http {
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Rate Limit Zone 정의
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
limit_req_zone $binary_remote_addr
zone=general_limit:10m # 메모리 10MB (약 16만 IP 추적 가능)
rate=10r/s; # IP당 초당 10 요청
limit_req_zone $binary_remote_addr
zone=login_limit:10m
rate=3r/m; # 로그인은 분당 3회로 엄격 제한
limit_req_zone $binary_remote_addr
zone=api_limit:10m
rate=100r/m; # API는 분당 100회
server {
# 일반 요청 제한
location / {
limit_req zone=general_limit
burst=20 # 순간 최대 허용 (대기 큐)
nodelay; # 큐 대기 없이 즉시 처리 또는 거부
proxy_pass http://backend;
}
# 로그인 엔드포인트 엄격 제한
location /api/auth/login {
limit_req zone=login_limit burst=5 nodelay;
limit_req_status 429; # Too Many Requests
proxy_pass http://backend;
}
# API Rate Limiting
location /api/ {
limit_req zone=api_limit burst=50 nodelay;
proxy_pass http://backend;
}
}
}
보안 헤더 설정
nginx
server {
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 보안 응답 헤더 (OWASP 권장)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
add_header X-Frame-Options "SAMEORIGIN" always;
# 클릭재킹 방지 (동일 출처 iframe만 허용)
add_header X-Content-Type-Options "nosniff" always;
# MIME 타입 스니핑 방지
add_header X-XSS-Protection "1; mode=block" always;
# 구형 브라우저 XSS 필터 활성화
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Referrer 헤더 노출 최소화
add_header Content-Security-Policy
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
always;
# CSP: 허용된 리소스 출처만 로드
# Nginx 버전 숨김 (정보 노출 방지)
server_tokens off;
}
6. 실전 완성형 설정 – 프로덕션 Nginx 템플릿
지금까지 다룬 내용을 하나의 완성형 설정으로 통합합니다.
nginx
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535; # 파일 디스크립터 한도 상향
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 기본 성능 설정
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
# 로그 포맷 (JSON 형식 - ELK 스택 연동에 유용)
log_format json_combined escape=json
'{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"method":"$request_method",'
'"uri":"$request_uri",'
'"status":$status,'
'"body_bytes":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_time":"$upstream_response_time",'
'"upstream_addr":"$upstream_addr",'
'"cache_status":"$upstream_cache_status"'
'}';
access_log /var/log/nginx/access.log json_combined;
# gzip 압축
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types text/plain text/css application/json
application/javascript text/xml application/xml
image/svg+xml;
# 캐시 영역 정의
proxy_cache_path /var/cache/nginx/api
levels=1:2
keys_zone=api_cache:20m
max_size=2g
inactive=60m
use_temp_path=off;
proxy_cache_path /var/cache/nginx/static
levels=1:2
keys_zone=static_cache:10m
max_size=5g
inactive=7d
use_temp_path=off;
# Rate Limiting Zone
limit_req_zone $binary_remote_addr zone=general:10m rate=20r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=200r/m;
# 쿠키·메서드 기반 캐시 우회 맵
map $http_cookie $bypass_cache {
~*sessionid 1;
~*auth_token 1;
default 0;
}
map $request_method $no_cache {
POST 1;
PUT 1;
DELETE 1;
PATCH 1;
default 0;
}
# WAS 클러스터 정의
upstream was_cluster {
least_conn;
server was1.internal:8080 weight=3 max_fails=3 fail_timeout=30s;
server was2.internal:8080 weight=3 max_fails=3 fail_timeout=30s;
server was3.internal:8080 weight=1 max_fails=3 fail_timeout=30s;
server backup.internal:8080 backup;
keepalive 32;
}
# HTTP → HTTPS 리다이렉트
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
# HTTPS 메인 서버
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 valid=300s;
# 보안 헤더
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 정적 파일 (Nginx 직접 처리)
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|css|js|woff|woff2)$ {
root /var/www/html;
proxy_cache static_cache;
proxy_cache_valid 200 7d;
expires 7d;
add_header Cache-Control "public";
add_header X-Cache-Status $upstream_cache_status;
}
# 로그인 (엄격한 Rate Limit)
location = /api/auth/login {
limit_req zone=login burst=5 nodelay;
limit_req_status 429;
proxy_pass http://was_cluster;
include /etc/nginx/proxy_params;
proxy_no_cache 1;
proxy_cache_bypass 1;
}
# API 엔드포인트
location /api/ {
limit_req zone=api burst=100 nodelay;
proxy_pass http://was_cluster;
include /etc/nginx/proxy_params;
proxy_cache api_cache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 5m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
proxy_cache_lock on;
proxy_cache_bypass $bypass_cache $no_cache;
proxy_no_cache $bypass_cache $no_cache;
add_header X-Cache-Status $upstream_cache_status;
proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 2;
}
# 기본 (프론트엔드 SPA)
location / {
limit_req zone=general burst=30 nodelay;
root /var/www/html;
try_files $uri $uri/ /index.html;
# SPA: 없는 경로는 index.html로 (클라이언트 라우팅)
}
# 내부 상태 모니터링
location /nginx_status {
stub_status on;
allow 10.0.0.0/8;
deny all;
}
}
}
설정 적용과 검증 명령어
bash
# 설정 파일 문법 검사 (적용 전 필수)
nginx -t
# 설정 파일 경로 지정 검사
nginx -t -c /etc/nginx/nginx.conf
# 무중단 설정 리로드 (서비스 중단 없이 적용)
nginx -s reload
# 캐시 디렉토리 생성 및 권한 설정
mkdir -p /var/cache/nginx/api /var/cache/nginx/static
chown -R nginx:nginx /var/cache/nginx
# 실시간 접근 로그 확인
tail -f /var/log/nginx/access.log | python3 -m json.tool
# 캐시 상태 확인 (X-Cache-Status 헤더)
curl -I https://example.com/api/users
# HIT: 캐시에서 응답 MISS: 백엔드에서 응답
# BYPASS: 캐시 우회 EXPIRED: 만료된 캐시
결론
Nginx 리버스 프록시 설정은 단순한 proxy_pass 한 줄이 아니라, location 매칭 우선순위 이해, upstream 알고리즘 선택, 캐시 키 설계, 보안 헤더·Rate Limiting·SSL 최적화가 모두 맞물려야 프로덕션에서 안정적으로 작동합니다. 이 글의 완성형 템플릿을 시작점으로 삼되, 서비스 특성에 따라 캐시 TTL·Rate Limit 수치·upstream 가중치를 조정하는 것이 실무의 핵심입니다. 가장 빠른 학습법은 로컬에서 Docker로 Nginx를 띄우고 X-Cache-Status 헤더를 직접 확인하며 캐시 HIT·MISS를 눈으로 검증하는 것입니다.
답글 남기기