Nginx 완전 가이드 – 리버스 프록시·로드밸런싱·캐시 실전 설정법


Nginx 리버스 프록시 설정은 현대 웹 아키텍처의 핵심 기술입니다. 전 세계 상위 1만 개 웹사이트의 절반 이상이 Nginx를 사용하며, 넷플릭스·깃허브·에어비앤비 같은 대형 서비스들도 Nginx를 프론트 레이어로 사용합니다. 단순히 “설치하면 된다”는 수준을 넘어서, location 블록 매칭 우선순위, upstream 로드밸런싱 알고리즘 선택 기준, 캐시 키 설계, Rate Limiting 전략까지 — 이 내용들을 정확히 알아야 프로덕션 환경에서 Nginx를 제대로 다룰 수 있습니다. 이 글에서는 Nginx의 설정 파일 구조부터 실무에서 바로 쓸 수 있는 완성형 설정까지 단계별로 정리합니다.


목차

  1. Nginx 기초 – 동작 원리와 설정 파일 구조
  2. 리버스 프록시 – proxy_pass와 location 블록 완전 정복
  3. 로드밸런싱 – upstream 알고리즘과 헬스체크 설정
  4. 캐시 – proxy_cache로 응답 속도 극대화
  5. 보안·성능 최적화 – SSL·gzip·Rate Limiting
  6. 실전 완성형 설정 – 프로덕션 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를 눈으로 검증하는 것입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다