Nginx 완벽 가이드 | 설치·설정·리버스 프록시·로드 밸런싱 총정리

Nginx 완벽 가이드 | 설치·설정·리버스 프록시·로드 밸런싱 총정리

이 글의 핵심

Nginx 웹 서버의 모든 것. 설치부터 리버스 프록시, 로드 밸런싱, SSL/TLS, 캐싱, 보안 설정까지. Apache와의 비교, 성능 튜닝, 실전 설정 예제로 완벽 마스터.

들어가며: Nginx가 필요한 이유

Nginx(엔진엑스)는 전 세계 상위 1,000만 웹사이트 중 약 33%가 사용하는 고성능 웹 서버입니다. 정적 파일 서빙, 리버스 프록시, 로드 밸런싱, SSL 종료 등 현대 웹 인프라의 핵심 역할을 담당합니다.

Nginx의 주요 용도:

  • 웹 서버 (정적 파일 서빙)
  • 리버스 프록시
  • 로드 밸런서
  • API 게이트웨이
  • SSL/TLS 종료
  • 캐싱 서버

목차

  1. Nginx 기본 개념
  2. 설치 및 기본 설정
  3. Nginx 아키텍처
  4. 정적 파일 서빙
  5. 리버스 프록시
  6. 로드 밸런싱
  7. SSL/TLS 설정
  8. 캐싱
  9. 보안 설정
  10. 성능 튜닝
  11. 실전 시나리오

1. Nginx 기본 개념

Nginx란?

Nginx(Engine X)는 Igor Sysoev가 2004년 개발한 고성능 웹 서버이자 리버스 프록시 서버입니다.

Nginx vs Apache

flowchart TB
    subgraph Nginx[Nginx 아키텍처]
        N_Master[Master Process]
        N_W1[Worker 1<br/>이벤트 기반]
        N_W2[Worker 2<br/>이벤트 기반]
        
        N_Master --> N_W1
        N_Master --> N_W2
        
        N_W1 --> N_C1[10,000 연결]
        N_W2 --> N_C2[10,000 연결]
    end
    
    subgraph Apache[Apache 아키텍처]
        A_Master[Master Process]
        A_P1[Process 1]
        A_P2[Process 2]
        A_P3[Process 3]
        
        A_Master --> A_P1
        A_Master --> A_P2
        A_Master --> A_P3
        
        A_P1 --> A_C1[1 연결]
        A_P2 --> A_C2[1 연결]
        A_P3 --> A_C3[1 연결]
    end

비교표

특성NginxApache
아키텍처이벤트 기반 비동기프로세스/스레드 기반
메모리낮음 (2-4MB/worker)높음 (10-20MB/process)
동시 연결수만 개수백 개
정적 파일매우 빠름빠름
동적 콘텐츠프록시 필요직접 처리 (.htaccess)
설정 파일간결복잡 (.htaccess)
모듈컴파일 시 포함동적 로딩
사용 사례고트래픽, 정적 파일복잡한 동적 처리

2. 설치 및 기본 설정

설치

Ubuntu/Debian

# 공식 저장소 추가
sudo apt update
sudo apt install curl gnupg2 ca-certificates lsb-release ubuntu-keyring

curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
    | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \
    | sudo tee /etc/apt/sources.list.d/nginx.list

# 설치
sudo apt update
sudo apt install nginx

# 버전 확인
nginx -v
# nginx version: nginx/1.24.0

CentOS/RHEL

# 저장소 추가
sudo yum install yum-utils
sudo yum-config-manager --add-repo https://nginx.org/packages/centos/nginx.repo

# 설치
sudo yum install nginx

# 시작
sudo systemctl start nginx
sudo systemctl enable nginx

macOS

# Homebrew로 설치
brew install nginx

# 시작
brew services start nginx

# 설정 파일 위치
# /usr/local/etc/nginx/nginx.conf

Docker

# Docker로 실행
docker run -d \
  --name nginx \
  -p 80:80 \
  -v $(pwd)/html:/usr/share/nginx/html:ro \
  nginx:latest

# 커스텀 설정
docker run -d \
  --name nginx \
  -p 80:80 \
  -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \
  -v $(pwd)/html:/usr/share/nginx/html:ro \
  nginx:latest

기본 명령어

# 시작
sudo systemctl start nginx
sudo nginx

# 중지
sudo systemctl stop nginx
sudo nginx -s stop

# 재시작
sudo systemctl restart nginx
sudo nginx -s reload  # 설정 리로드 (무중단)

# 상태 확인
sudo systemctl status nginx

# 설정 테스트
sudo nginx -t

# 버전 및 모듈 확인
nginx -V

3. Nginx 아키텍처

프로세스 모델

flowchart TB
    Master[Master Process<br/>root 권한]
    
    W1[Worker Process 1<br/>nginx 사용자]
    W2[Worker Process 2<br/>nginx 사용자]
    W3[Worker Process 3<br/>nginx 사용자]
    W4[Worker Process 4<br/>nginx 사용자]
    
    Cache[Cache Loader<br/>Cache Manager]
    
    Master --> W1
    Master --> W2
    Master --> W3
    Master --> W4
    Master --> Cache
    
    subgraph Connections[클라이언트 연결]
        C1[10,000 연결]
        C2[10,000 연결]
        C3[10,000 연결]
        C4[10,000 연결]
    end
    
    W1 --> C1
    W2 --> C2
    W3 --> C3
    W4 --> C4

이벤트 기반 처리

sequenceDiagram
    participant C1 as Client 1
    participant C2 as Client 2
    participant C3 as Client 3
    participant W as Worker Process
    participant Backend as Backend Server
    
    Note over W: epoll/kqueue로<br/>이벤트 감시
    
    C1->>W: HTTP Request
    W->>Backend: Proxy Request
    
    C2->>W: HTTP Request (동시)
    W->>Backend: Proxy Request (동시)
    
    C3->>W: HTTP Request (동시)
    
    Backend->>W: Response for C1
    W->>C1: HTTP Response
    
    Backend->>W: Response for C2
    W->>C2: HTTP Response
    
    W->>Backend: Proxy Request for C3
    Backend->>W: Response for C3
    W->>C3: HTTP Response
    
    Note over W: 하나의 프로세스가<br/>모든 연결 처리

설정 파일 구조

# /etc/nginx/nginx.conf

# 메인 컨텍스트
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

# 이벤트 컨텍스트
events {
    worker_connections 1024;
    use epoll;  # Linux
}

# HTTP 컨텍스트
http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    # 로그 포맷
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
    
    access_log /var/log/nginx/access.log main;
    
    # 성능 설정
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    
    # Gzip 압축
    gzip on;
    gzip_types text/plain text/css application/json application/javascript;
    
    # 서버 블록 포함
    include /etc/nginx/conf.d/*.conf;
}

4. 정적 파일 서빙

기본 웹 서버 설정

# /etc/nginx/conf.d/static.conf

server {
    listen 80;
    server_name example.com www.example.com;
    
    # 루트 디렉토리
    root /var/www/html;
    index index.html index.htm;
    
    # 접근 로그
    access_log /var/log/nginx/example.access.log;
    error_log /var/log/nginx/example.error.log;
    
    # 기본 location
    location / {
        try_files $uri $uri/ =404;
    }
    
    # 정적 파일 캐싱
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # 특정 파일 차단
    location ~ /\.(?!well-known) {
        deny all;
    }
}

SPA (Single Page Application) 설정

# React, Vue, Angular 등
server {
    listen 80;
    server_name app.example.com;
    
    root /var/www/app/dist;
    index index.html;
    
    # SPA 라우팅 (모든 요청을 index.html로)
    location / {
        try_files $uri $uri/ /index.html;
    }
    
    # API 프록시
    location /api/ {
        proxy_pass http://localhost:3000/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
    
    # 정적 자산 캐싱
    location /static/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

5. 리버스 프록시

리버스 프록시란?

flowchart LR
    Client[클라이언트] --> Nginx[Nginx<br/>리버스 프록시<br/>:80]
    
    Nginx --> Backend1[Backend 1<br/>:3000]
    Nginx --> Backend2[Backend 2<br/>:3001]
    Nginx --> Backend3[Backend 3<br/>:3002]
    
    Nginx --> Static[정적 파일<br/>/var/www]
    
    style Nginx fill:#4CAF50

기본 리버스 프록시 설정

server {
    listen 80;
    server_name api.example.com;
    
    # 백엔드로 프록시
    location / {
        proxy_pass http://localhost:3000;
        
        # 필수 헤더
        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 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # 버퍼링
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        proxy_busy_buffers_size 8k;
    }
}

WebSocket 프록시

# WebSocket 지원
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

server {
    listen 80;
    server_name ws.example.com;
    
    location / {
        proxy_pass http://localhost:3000;
        
        # WebSocket 헤더
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        
        # 타임아웃 (WebSocket은 길게)
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

gRPC 프록시

server {
    listen 80 http2;
    server_name grpc.example.com;
    
    location / {
        grpc_pass grpc://localhost:50051;
        
        # gRPC 헤더
        grpc_set_header Host $host;
        grpc_set_header X-Real-IP $remote_addr;
        
        # 에러 처리
        error_page 502 = /error502grpc;
    }
    
    location = /error502grpc {
        internal;
        default_type application/grpc;
        add_header grpc-status 14;
        add_header grpc-message "unavailable";
        return 204;
    }
}

6. 로드 밸런싱

업스트림 설정

# 백엔드 서버 그룹 정의
upstream backend {
    # 로드 밸런싱 알고리즘: round-robin (기본값)
    server backend1.example.com:8080 weight=3;
    server backend2.example.com:8080 weight=2;
    server backend3.example.com:8080 weight=1;
    
    # 백업 서버
    server backup.example.com:8080 backup;
    
    # 헬스 체크 실패 시 제외
    server backend4.example.com:8080 max_fails=3 fail_timeout=30s;
    
    # Keep-Alive 연결 유지
    keepalive 32;
}

server {
    listen 80;
    server_name api.example.com;
    
    location / {
        proxy_pass http://backend;
        
        # Keep-Alive 헤더
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

로드 밸런싱 알고리즘

# 1. Round Robin (기본값)
upstream backend_rr {
    server backend1:8080;
    server backend2:8080;
    server backend3:8080;
}

# 2. Least Connections (연결 수 기반)
upstream backend_lc {
    least_conn;
    
    server backend1:8080;
    server backend2:8080;
    server backend3:8080;
}

# 3. IP Hash (세션 고정)
upstream backend_ip {
    ip_hash;
    
    server backend1:8080;
    server backend2:8080;
    server backend3:8080;
}

# 4. Hash (커스텀 키)
upstream backend_hash {
    hash $request_uri consistent;
    
    server backend1:8080;
    server backend2:8080;
    server backend3:8080;
}

# 5. Random (무작위)
upstream backend_random {
    random two least_conn;  # 2개 중 연결 적은 것 선택
    
    server backend1:8080;
    server backend2:8080;
    server backend3:8080;
}

헬스 체크

# 기본 헬스 체크 (수동)
upstream backend {
    server backend1:8080 max_fails=3 fail_timeout=30s;
    server backend2:8080 max_fails=3 fail_timeout=30s;
    
    # max_fails: 실패 횟수
    # fail_timeout: 실패 후 재시도까지 대기 시간
}

# Nginx Plus (상용)에서는 능동적 헬스 체크 지원
# 오픈소스는 외부 모듈 필요

7. SSL/TLS 설정

Let’s Encrypt SSL 설정

# Certbot 설치
sudo apt install certbot python3-certbot-nginx

# SSL 인증서 발급
sudo certbot --nginx -d example.com -d www.example.com

# 자동 갱신 설정
sudo certbot renew --dry-run

SSL 설정 파일

server {
    listen 80;
    server_name example.com www.example.com;
    
    # HTTP → HTTPS 리다이렉트
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;
    
    # SSL 인증서
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
    # SSL 프로토콜
    ssl_protocols TLSv1.2 TLSv1.3;
    
    # SSL 암호화 스위트 (강력한 것만)
    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 세션 캐시
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_session_tickets off;
    
    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    
    # 보안 헤더
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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;
    
    root /var/www/html;
    index index.html;
    
    location / {
        try_files $uri $uri/ =404;
    }
}

SSL 성능 최적화

# SSL 세션 재사용
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;

# OCSP Stapling (SSL 핸드셰이크 속도 향상)
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

# HTTP/2 활성화
listen 443 ssl http2;

# SSL 버퍼 크기 조정
ssl_buffer_size 4k;  # 작은 파일에 최적화

8. 캐싱

프록시 캐시 설정

# HTTP 컨텍스트에서 캐시 경로 정의
http {
    # 캐시 저장 경로
    proxy_cache_path /var/cache/nginx/proxy
                     levels=1:2
                     keys_zone=my_cache:10m
                     max_size=1g
                     inactive=60m
                     use_temp_path=off;
    
    server {
        listen 80;
        server_name example.com;
        
        location / {
            proxy_pass http://backend;
            
            # 캐시 활성화
            proxy_cache my_cache;
            
            # 캐시 키
            proxy_cache_key "$scheme$request_method$host$request_uri";
            
            # 캐시 유효 시간
            proxy_cache_valid 200 302 10m;
            proxy_cache_valid 404 1m;
            
            # 캐시 우회 조건
            proxy_cache_bypass $http_cache_control;
            
            # 캐시 상태 헤더 추가
            add_header X-Cache-Status $upstream_cache_status;
            
            # 캐시 락 (동일 요청 중복 방지)
            proxy_cache_lock on;
            proxy_cache_lock_timeout 5s;
        }
    }
}

캐시 제어

location /api/ {
    proxy_pass http://backend;
    proxy_cache my_cache;
    
    # 특정 조건에서 캐시 우회
    proxy_cache_bypass $cookie_nocache $arg_nocache;
    
    # 특정 조건에서 캐시 안 함
    proxy_no_cache $cookie_nocache $arg_nocache;
    
    # 캐시 메서드
    proxy_cache_methods GET HEAD;
    
    # 최소 사용 횟수 (2번 이상 요청된 것만 캐시)
    proxy_cache_min_uses 2;
}

# 캐시 퍼지 (Nginx Plus 또는 모듈 필요)
location ~ /purge(/.*) {
    allow 127.0.0.1;
    deny all;
    proxy_cache_purge my_cache "$scheme$request_method$host$1";
}

FastCGI 캐시 (PHP)

# FastCGI 캐시 경로
fastcgi_cache_path /var/cache/nginx/fastcgi
                   levels=1:2
                   keys_zone=php_cache:10m
                   max_size=1g
                   inactive=60m;

server {
    listen 80;
    server_name example.com;
    
    root /var/www/html;
    index index.php;
    
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        
        # FastCGI 캐시
        fastcgi_cache php_cache;
        fastcgi_cache_key "$scheme$request_method$host$request_uri";
        fastcgi_cache_valid 200 60m;
        
        # 캐시 우회 (로그인 사용자, POST 요청)
        fastcgi_cache_bypass $cookie_user $request_method;
        fastcgi_no_cache $cookie_user $request_method;
        
        add_header X-Cache-Status $upstream_cache_status;
    }
}

9. 보안 설정

기본 보안 강화

server {
    listen 443 ssl http2;
    server_name example.com;
    
    # 서버 버전 숨기기
    server_tokens off;
    
    # 보안 헤더
    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 "no-referrer-when-downgrade" always;
    add_header Content-Security-Policy "default-src 'self' https:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
    
    # HSTS (Strict-Transport-Security)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    
    # 클릭재킹 방지
    add_header X-Frame-Options "DENY" always;
    
    # 파일 업로드 크기 제한
    client_max_body_size 10M;
    
    # 요청 속도 제한
    limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
    limit_req zone=one burst=20 nodelay;
    
    location / {
        root /var/www/html;
    }
}

Rate Limiting (요청 속도 제한)

# HTTP 컨텍스트
http {
    # IP별 요청 속도 제한
    limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
    
    # API 엔드포인트별 제한
    limit_req_zone $request_uri zone=api:10m rate=5r/s;
    
    # 동시 연결 수 제한
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    
    server {
        listen 80;
        
        # 일반 페이지: 초당 10개 요청, 버스트 20개
        location / {
            limit_req zone=general burst=20 nodelay;
            limit_conn addr 10;
        }
        
        # API: 초당 5개 요청, 버스트 10개
        location /api/ {
            limit_req zone=api burst=10 nodelay;
            limit_conn addr 5;
            
            # 제한 초과 시 응답
            limit_req_status 429;
            limit_conn_status 429;
        }
        
        # 로그인: 초당 1개 요청
        location /login {
            limit_req zone=general burst=5;
        }
    }
}

IP 화이트리스트/블랙리스트

# 특정 IP 차단
location /admin/ {
    # 허용 IP
    allow 192.168.1.0/24;
    allow 10.0.0.0/8;
    
    # 나머지 차단
    deny all;
    
    proxy_pass http://backend;
}

# GeoIP로 국가별 차단 (모듈 필요)
http {
    geoip_country /usr/share/GeoIP/GeoIP.dat;
    
    map $geoip_country_code $allowed_country {
        default no;
        KR yes;  # 한국만 허용
        US yes;  # 미국 허용
    }
    
    server {
        listen 80;
        
        if ($allowed_country = no) {
            return 403 "Access denied from your country";
        }
        
        location / {
            root /var/www/html;
        }
    }
}

DDoS 방어

# HTTP 컨텍스트
http {
    # 연결 속도 제한
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
    limit_req_zone $binary_remote_addr zone=req_limit:10m rate=5r/s;
    
    # 느린 요청 차단
    client_body_timeout 10s;
    client_header_timeout 10s;
    send_timeout 10s;
    
    # 큰 요청 차단
    client_max_body_size 1M;
    client_body_buffer_size 128k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 4k;
    
    server {
        listen 80;
        
        # 연결 제한 적용
        limit_conn conn_limit 10;
        limit_req zone=req_limit burst=10 nodelay;
        
        # User-Agent 검증
        if ($http_user_agent ~* (bot|crawler|spider|scraper)) {
            return 403;
        }
        
        # Referer 검증
        valid_referers none blocked example.com *.example.com;
        if ($invalid_referer) {
            return 403;
        }
        
        location / {
            root /var/www/html;
        }
    }
}

10. 성능 튜닝

Worker 프로세스 최적화

# CPU 코어 수만큼 워커 생성
worker_processes auto;

# CPU 친화성 (각 워커를 특정 CPU에 바인딩)
worker_cpu_affinity auto;

# 워커당 최대 연결 수
events {
    worker_connections 4096;  # 기본값: 1024
    
    # 이벤트 모델
    use epoll;  # Linux
    # use kqueue;  # FreeBSD/macOS
    
    # 여러 연결을 한 번에 accept
    multi_accept on;
}

# 최대 동시 연결 수 = worker_processes × worker_connections
# 예: 4 × 4096 = 16,384 연결

파일 I/O 최적화

http {
    # Sendfile (커널 공간에서 직접 전송)
    sendfile on;
    
    # TCP_NOPUSH (sendfile과 함께 사용)
    tcp_nopush on;
    
    # TCP_NODELAY (작은 패킷 즉시 전송)
    tcp_nodelay on;
    
    # 파일 디스크립터 캐시
    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
    
    # 버퍼 크기
    client_body_buffer_size 128k;
    client_max_body_size 10m;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 4k;
    output_buffers 1 32k;
    postpone_output 1460;
}

Gzip 압축

http {
    # Gzip 압축 활성화
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;  # 1-9 (높을수록 압축률 높지만 CPU 사용 증가)
    gzip_min_length 256;
    
    # 압축할 MIME 타입
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/json
        application/javascript
        application/xml+rss
        application/rss+xml
        application/atom+xml
        image/svg+xml
        text/x-component
        text/x-cross-domain-policy;
    
    # 압축 안 할 브라우저 (구형 IE)
    gzip_disable "msie6";
}

Brotli 압축 (Gzip보다 효율적)

# Brotli 모듈 설치 필요
http {
    brotli on;
    brotli_comp_level 6;
    brotli_types
        text/plain
        text/css
        application/json
        application/javascript
        text/xml
        application/xml
        application/xml+rss
        text/javascript;
}

11. 실전 시나리오

시나리오 1: Node.js 앱 배포

# Node.js 앱 (Express) 프록시
upstream nodejs_backend {
    least_conn;
    
    server localhost:3000;
    server localhost:3001;
    server localhost:3002;
    
    keepalive 64;
}

server {
    listen 443 ssl http2;
    server_name app.example.com;
    
    # SSL 설정
    ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
    
    # 정적 파일 (빌드된 프론트엔드)
    location / {
        root /var/www/app/dist;
        try_files $uri $uri/ /index.html;
        
        # 캐싱
        expires 1h;
        add_header Cache-Control "public, immutable";
    }
    
    # API 프록시
    location /api/ {
        proxy_pass http://nodejs_backend/;
        
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # 캐시 비활성화 (동적 API)
        proxy_cache_bypass 1;
        proxy_no_cache 1;
    }
    
    # WebSocket
    location /socket.io/ {
        proxy_pass http://nodejs_backend/socket.io/;
        
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        
        proxy_read_timeout 86400s;
    }
}

시나리오 2: 마이크로서비스 API 게이트웨이

# 여러 마이크로서비스 프록시
upstream auth_service {
    server auth:8080;
}

upstream user_service {
    server user:8081;
}

upstream order_service {
    server order:8082;
}

upstream payment_service {
    server payment:8083;
}

server {
    listen 80;
    server_name api.example.com;
    
    # 공통 설정
    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;
    
    # Rate Limiting
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
    limit_req zone=api_limit burst=200 nodelay;
    
    # 인증 서비스
    location /api/auth/ {
        proxy_pass http://auth_service/;
    }
    
    # 사용자 서비스
    location /api/users/ {
        # 인증 확인
        auth_request /auth;
        
        proxy_pass http://user_service/;
    }
    
    # 주문 서비스
    location /api/orders/ {
        auth_request /auth;
        proxy_pass http://order_service/;
        
        # 타임아웃 (결제 처리는 길게)
        proxy_read_timeout 120s;
    }
    
    # 결제 서비스
    location /api/payments/ {
        auth_request /auth;
        proxy_pass http://payment_service/;
        
        # HTTPS만 허용
        if ($scheme != "https") {
            return 301 https://$server_name$request_uri;
        }
    }
    
    # 내부 인증 엔드포인트
    location = /auth {
        internal;
        proxy_pass http://auth_service/verify;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
    }
    
    # 헬스 체크
    location /health {
        access_log off;
        return 200 "OK\n";
        add_header Content-Type text/plain;
    }
}

시나리오 3: 정적 사이트 + CDN

server {
    listen 443 ssl http2;
    server_name cdn.example.com;
    
    root /var/www/cdn;
    
    # 브라우저 캐싱
    location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header Vary "Accept-Encoding";
        
        # CORS 허용
        add_header Access-Control-Allow-Origin "*";
        add_header Access-Control-Allow-Methods "GET, OPTIONS";
    }
    
    location ~* \.(css|js)$ {
        expires 1M;
        add_header Cache-Control "public";
        add_header Vary "Accept-Encoding";
    }
    
    location ~* \.(woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header Access-Control-Allow-Origin "*";
    }
    
    # 이미지 최적화 (ngx_http_image_filter_module)
    location /images/ {
        image_filter_buffer 10M;
        
        # 썸네일 생성
        location ~ /images/thumb/(.+)$ {
            image_filter resize 300 300;
            image_filter_jpeg_quality 85;
        }
    }
}

고급 설정

로깅 최적화

http {
    # 로그 포맷 정의
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
    
    # JSON 로그 포맷
    log_format json escape=json '{'
        '"time": "$time_iso8601",'
        '"remote_addr": "$remote_addr",'
        '"request": "$request",'
        '"status": $status,'
        '"body_bytes_sent": $body_bytes_sent,'
        '"request_time": $request_time,'
        '"upstream_response_time": "$upstream_response_time",'
        '"user_agent": "$http_user_agent"'
    '}';
    
    # 조건부 로깅 (헬스 체크 제외)
    map $request_uri $loggable {
        /health 0;
        /metrics 0;
        default 1;
    }
    
    server {
        listen 80;
        
        # 조건부 로깅
        access_log /var/log/nginx/access.log main if=$loggable;
        
        # JSON 로그
        access_log /var/log/nginx/access.json.log json;
        
        # 버퍼링 (성능 향상)
        access_log /var/log/nginx/access.log main buffer=32k flush=5s;
    }
}

리다이렉트 규칙

server {
    listen 80;
    server_name example.com www.example.com;
    
    # HTTP → HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;
    
    # www → non-www
    if ($host = www.example.com) {
        return 301 https://example.com$request_uri;
    }
    
    # 특정 경로 리다이렉트
    location /old-page {
        return 301 /new-page;
    }
    
    # 정규식 리다이렉트
    location ~ ^/blog/(\d+)/(.+)$ {
        return 301 /posts/$1/$2;
    }
    
    # 쿼리 스트링 유지
    location /search {
        return 301 /new-search$is_args$args;
    }
}

커스텀 에러 페이지

server {
    listen 80;
    server_name example.com;
    
    # 에러 페이지 정의
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
    
    location = /404.html {
        root /var/www/errors;
        internal;
    }
    
    location = /50x.html {
        root /var/www/errors;
        internal;
    }
    
    # 백엔드 에러 시 커스텀 응답
    location /api/ {
        proxy_pass http://backend;
        
        proxy_intercept_errors on;
        error_page 502 503 504 = @backend_error;
    }
    
    location @backend_error {
        return 503 '{"error": "Service temporarily unavailable"}';
        add_header Content-Type application/json;
    }
}

Docker Compose로 Nginx 스택

version: '3.8'

services:
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./conf.d:/etc/nginx/conf.d:ro
      - ./html:/usr/share/nginx/html:ro
      - ./ssl:/etc/nginx/ssl:ro
      - ./logs:/var/log/nginx
    depends_on:
      - app1
      - app2
    networks:
      - webnet
    restart: unless-stopped
  
  app1:
    image: node:18
    working_dir: /app
    volumes:
      - ./app:/app
    command: npm start
    environment:
      - PORT=3000
    networks:
      - webnet
  
  app2:
    image: node:18
    working_dir: /app
    volumes:
      - ./app:/app
    command: npm start
    environment:
      - PORT=3000
    networks:
      - webnet

networks:
  webnet:
    driver: bridge

volumes:
  nginx-cache:

모니터링

Nginx 상태 모듈

# stub_status 모듈 활성화
server {
    listen 80;
    server_name localhost;
    
    location /nginx_status {
        stub_status;
        
        # 로컬에서만 접근 허용
        allow 127.0.0.1;
        deny all;
    }
}
# 상태 확인
curl http://localhost/nginx_status

# 출력:
# Active connections: 291
# server accepts handled requests
#  16630948 16630948 31070465
# Reading: 6 Writing: 179 Keeping: 106

Prometheus 메트릭

# nginx-prometheus-exporter 사용
# https://github.com/nginxinc/nginx-prometheus-exporter

# Docker로 실행
docker run -d \
  --name nginx-exporter \
  -p 9113:9113 \
  nginx/nginx-prometheus-exporter:latest \
  -nginx.scrape-uri=http://nginx/nginx_status

로그 분석

# 접근 로그 분석
# 1. 상위 IP 주소
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10

# 2. 상위 요청 URL
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10

# 3. HTTP 상태 코드 분포
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

# 4. 평균 응답 시간
awk '{sum+=$NF; count++} END {print sum/count}' /var/log/nginx/access.log

# 5. 5xx 에러 찾기
grep " 5[0-9][0-9] " /var/log/nginx/access.log

# GoAccess로 실시간 분석
goaccess /var/log/nginx/access.log -o /var/www/html/report.html --log-format=COMBINED

문제 해결

일반적인 문제

문제 1: 502 Bad Gateway

# 원인:
# - 백엔드 서버 다운
# - 타임아웃
# - 버퍼 크기 부족

# 해결:
location / {
    proxy_pass http://backend;
    
    # 타임아웃 증가
    proxy_connect_timeout 75s;
    proxy_read_timeout 300s;
    
    # 버퍼 크기 증가
    proxy_buffer_size 4k;
    proxy_buffers 8 4k;
    proxy_busy_buffers_size 8k;
    
    # 재시도
    proxy_next_upstream error timeout http_502 http_503 http_504;
}

문제 2: 413 Request Entity Too Large

# 원인: 파일 업로드 크기 제한

# 해결:
http {
    client_max_body_size 100M;  # 전역 설정
}

server {
    location /upload {
        client_max_body_size 500M;  # 특정 경로만
    }
}

문제 3: 느린 응답

# 디버깅
# 1. 백엔드 응답 시간 확인
tail -f /var/log/nginx/access.log | grep -oP 'upstream_response_time: \K[\d.]+'

# 2. Nginx 설정 테스트
sudo nginx -t

# 3. 워커 프로세스 상태
ps aux | grep nginx

# 4. 연결 수 확인
netstat -an | grep :80 | wc -l

# 5. 에러 로그 확인
tail -f /var/log/nginx/error.log

베스트 프랙티스

설정 파일 구조

/etc/nginx/
├── nginx.conf              # 메인 설정
├── conf.d/
   ├── default.conf        # 기본 서버
   ├── api.conf            # API 서버
   └── static.conf         # 정적 파일 서버
├── sites-available/        # 사용 가능한 사이트
   ├── example.com
   └── app.example.com
├── sites-enabled/          # 활성화된 사이트 (심볼릭 링크)
   ├── example.com -> ../sites-available/example.com
   └── app.example.com -> ../sites-available/app.example.com
├── snippets/               # 재사용 가능한 설정
   ├── ssl-params.conf
   ├── proxy-params.conf
   └── security-headers.conf
└── modules-enabled/        # 활성화된 모듈

재사용 가능한 스니펫

# /etc/nginx/snippets/proxy-params.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 $scheme;
proxy_http_version 1.1;
proxy_set_header Connection "";

# /etc/nginx/snippets/ssl-params.conf
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 10m;
ssl_stapling on;
ssl_stapling_verify on;

# /etc/nginx/snippets/security-headers.conf
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 "no-referrer-when-downgrade" always;

# 사용
server {
    listen 443 ssl http2;
    server_name example.com;
    
    include snippets/ssl-params.conf;
    include snippets/security-headers.conf;
    
    location / {
        proxy_pass http://backend;
        include snippets/proxy-params.conf;
    }
}

환경별 설정

# 개발 환경
# /etc/nginx/conf.d/dev.conf
server {
    listen 80;
    server_name dev.example.com;
    
    # 에러 표시 (개발 환경)
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
    
    # 디버그 로그
    error_log /var/log/nginx/dev.error.log debug;
    
    location / {
        proxy_pass http://localhost:3000;
        
        # CORS 허용 (개발 환경)
        add_header Access-Control-Allow-Origin "*";
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
        add_header Access-Control-Allow-Headers "Authorization, Content-Type";
    }
}

# 프로덕션 환경
# /etc/nginx/conf.d/prod.conf
server {
    listen 443 ssl http2;
    server_name example.com;
    
    # 보안 강화
    include snippets/ssl-params.conf;
    include snippets/security-headers.conf;
    
    # 에러 페이지 숨기기
    error_page 500 502 503 504 /error.html;
    location = /error.html {
        root /var/www/errors;
        internal;
    }
    
    # 에러 로그 (warn 레벨)
    error_log /var/log/nginx/prod.error.log warn;
    
    location / {
        proxy_pass http://backend;
        include snippets/proxy-params.conf;
    }
}

Nginx Plus (상용 버전)

Nginx Plus 기능

# 능동적 헬스 체크
upstream backend {
    zone backend 64k;
    
    server backend1:8080;
    server backend2:8080;
    
    # 헬스 체크
    health_check interval=5s fails=3 passes=2 uri=/health;
}

# 동적 업스트림 재설정 (API)
server {
    listen 8080;
    
    location /api {
        api write=on;
        allow 127.0.0.1;
        deny all;
    }
    
    # 대시보드
    location = /dashboard.html {
        root /usr/share/nginx/html;
    }
}

# 고급 캐싱
proxy_cache_path /var/cache/nginx
                 levels=1:2
                 keys_zone=cache:10m
                 max_size=10g
                 inactive=60m
                 use_temp_path=off
                 purger=on;  # Nginx Plus

성능 벤치마크

Apache Bench로 테스트

# 동시 연결 100개, 총 10,000 요청
ab -n 10000 -c 100 http://localhost/

# 결과 예시:
# Requests per second:    5000 [#/sec]
# Time per request:       20.0 [ms]
# Transfer rate:          1000 [Kbytes/sec]

# Keep-Alive 테스트
ab -n 10000 -c 100 -k http://localhost/

# POST 요청 테스트
ab -n 1000 -c 10 -p data.json -T application/json http://localhost/api/

wrk로 부하 테스트

# 12 스레드, 400 연결, 30초 동안
wrk -t12 -c400 -d30s http://localhost/

# Lua 스크립트로 복잡한 시나리오
wrk -t12 -c400 -d30s -s script.lua http://localhost/api/

# script.lua
wrk.method = "POST"
wrk.body = '{"key":"value"}'
wrk.headers["Content-Type"] = "application/json"
wrk.headers["Authorization"] = "Bearer token123"

정리

Nginx 사용 시나리오

flowchart TD
    Start[웹 서비스 구축] --> Q1{용도는?}
    
    Q1 -->|정적 파일| Static[✅ Nginx<br/>정적 파일 서버]
    Q1 -->|API 서버| Q2{백엔드는?}
    Q1 -->|마이크로서비스| Gateway[✅ Nginx<br/>API 게이트웨이]
    
    Q2 -->|Node.js/Python| Proxy[✅ Nginx<br/>리버스 프록시]
    Q2 -->|PHP| PHP[Nginx + PHP-FPM]
    
    Static --> CDN{CDN 필요?}
    CDN -->|Yes| CloudFlare[Nginx + CloudFlare]
    CDN -->|No| Direct[Nginx 직접]
    
    Proxy --> LB{로드 밸런싱?}
    LB -->|Yes| Multiple[Nginx<br/>+ 여러 백엔드]
    LB -->|No| Single[Nginx<br/>+ 단일 백엔드]

핵심 설정 요약

# 최소 프로덕션 설정
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;

events {
    worker_connections 4096;
    use epoll;
}

http {
    # 기본 설정
    include mime.types;
    default_type application/octet-stream;
    
    # 성능
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    
    # 압축
    gzip on;
    gzip_types text/plain text/css application/json application/javascript;
    
    # 보안
    server_tokens off;
    client_max_body_size 10M;
    
    # 로깅
    access_log /var/log/nginx/access.log;
    
    # 서버 블록
    include /etc/nginx/conf.d/*.conf;
}

체크리스트

설치 후

  • nginx -t로 설정 검증
  • 방화벽 포트 오픈 (80, 443)
  • SELinux 설정 (CentOS/RHEL)
  • 로그 디렉토리 권한 확인

보안

  • SSL/TLS 설정
  • server_tokens off
  • Rate Limiting 설정
  • 보안 헤더 추가
  • IP 화이트리스트 (관리자 페이지)

성능

  • worker_processes auto
  • worker_connections 증가
  • Gzip 압축 활성화
  • 캐싱 설정
  • Keep-Alive 활성화

모니터링

  • 접근/에러 로그 설정
  • stub_status 활성화
  • 로그 로테이션 설정
  • 모니터링 도구 연동

참고 자료

한 줄 요약: Nginx는 이벤트 기반 비동기 아키텍처로 적은 리소스로 수만 개의 동시 연결을 처리할 수 있어, 정적 파일 서빙, 리버스 프록시, 로드 밸런싱에 최적화된 고성능 웹 서버입니다.

---
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3