- Published on
Nginx 완전 가이드 2025: 리버스 프록시, 로드 밸런싱, SSL, 캐싱부터 보안까지
- Authors

- Name
- Youngju Kim
- @fjvbn20031
TOC
1. Nginx란 무엇인가
Nginx(엔진엑스)는 2004년 Igor Sysoev가 C10K 문제를 해결하기 위해 개발한 고성능 웹 서버입니다. 현재 전 세계 웹 서버 시장 점유율 1위를 차지하며, 단순 웹 서버를 넘어 리버스 프록시, 로드 밸런서, HTTP 캐시, API 게이트웨이 등 다양한 역할을 수행합니다.
1.1 Nginx vs Apache
| 항목 | Nginx | Apache |
|---|---|---|
| 아키텍처 | 이벤트 드리븐 (비동기) | 프로세스/스레드 기반 |
| 동시 연결 | 수만 ~ 수십만 | 수천 (MPM 설정에 따라) |
| 메모리 사용 | 연결당 수 KB | 연결당 수 MB |
| 정적 파일 | 매우 빠름 | 빠름 |
| 동적 컨텐츠 | 프록시 (FastCGI/uWSGI) | 모듈 내장 (mod_php) |
| 설정 방식 | 중앙 집중형 | .htaccess 분산 가능 |
| URL 재작성 | location 블록 | mod_rewrite |
| 로드 밸런싱 | 내장 | 별도 모듈 필요 |
| 시장 점유율 | 약 34% (1위) | 약 29% (2위) |
1.2 이벤트 드리븐 아키텍처
Apache (프로세스 모델):
┌────────────┐
│ Master │
├────────────┤
│ Worker 1 │ → Client A (1 process per connection)
│ Worker 2 │ → Client B
│ Worker 3 │ → Client C
│ ... │
│ Worker 1000│ → Client 1000
└────────────┘
→ 1000 connections = 1000 processes/threads = 높은 메모리 사용
Nginx (이벤트 모델):
┌────────────┐
│ Master │
├────────────┤
│ Worker 1 │ → epoll/kqueue로 수천 연결 처리
│ Worker 2 │ → (non-blocking I/O)
│ Worker 3 │ →
│ Worker 4 │ → (CPU 코어 수만큼)
└────────────┘
→ 10000 connections = 4 workers = 매우 낮은 메모리 사용
핵심 원리:
- Master Process: 설정 읽기, 워커 관리, 로그 관리
- Worker Process: 실제 요청 처리 (CPU 코어 수만큼 생성)
- epoll/kqueue: OS 레벨의 이벤트 통지 메커니즘
- Non-blocking I/O: 요청을 기다리지 않고 다른 요청 처리
1.3 Nginx 설치
# Ubuntu/Debian
sudo apt update
sudo apt install nginx
# CentOS/RHEL
sudo yum install epel-release
sudo yum install nginx
# macOS
brew install nginx
# Docker
docker run -d -p 80:80 -p 443:443 \
-v /path/to/nginx.conf:/etc/nginx/nginx.conf:ro \
-v /path/to/certs:/etc/nginx/certs:ro \
--name nginx nginx:alpine
# 상태 확인
sudo systemctl status nginx
nginx -t # 설정 문법 검사
nginx -V # 컴파일 옵션 확인
2. Nginx 설정 구조
2.1 설정 파일 구조
/etc/nginx/
├── nginx.conf # 메인 설정
├── conf.d/ # 추가 설정 (*.conf 자동 로드)
│ ├── default.conf
│ └── myapp.conf
├── sites-available/ # 사용 가능한 사이트 (Debian계열)
│ └── mysite.conf
├── sites-enabled/ # 활성화된 사이트 (심볼릭 링크)
│ └── mysite.conf -> ../sites-available/mysite.conf
├── mime.types # MIME 타입 매핑
├── fastcgi_params # FastCGI 파라미터
└── snippets/ # 재사용 가능한 설정 조각
├── ssl-params.conf
└── proxy-params.conf
2.2 설정 블록 계층
# /etc/nginx/nginx.conf
# 전역 컨텍스트
user nginx;
worker_processes auto; # CPU 코어 수만큼 자동 설정
worker_rlimit_nofile 65535; # 워커당 최대 파일 디스크립터
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
# 이벤트 컨텍스트
events {
worker_connections 10240; # 워커당 최대 동시 연결
multi_accept on; # 한 번에 여러 연결 수락
use epoll; # Linux: epoll, BSD: kqueue
}
# 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" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/access.log main;
# 성능 설정
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 50m;
# Gzip 압축
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_types text/plain text/css application/json
application/javascript text/xml application/xml
application/xml+rss text/javascript image/svg+xml;
# 서버 블록 포함
include /etc/nginx/conf.d/*.conf;
}
2.3 Server 블록과 Location 블록
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# 301 리다이렉트 (HTTP → HTTPS)
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
# SSL 설정
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
# 루트 디렉토리
root /var/www/html;
index index.html index.htm;
# Location 우선순위 (높은 순서)
# 1. 정확한 일치 (=)
location = /favicon.ico {
log_not_found off;
access_log off;
}
# 2. 우선 접두사 (^~)
location ^~ /static/ {
alias /var/www/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# 3. 정규식 (~, ~*) - 대소문자 구분/무시
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 7d;
add_header Cache-Control "public";
}
# 4. 접두사 일치 (없음 또는 /)
location / {
try_files $uri $uri/ /index.html;
}
# API 프록시
location /api/ {
proxy_pass http://backend;
include snippets/proxy-params.conf;
}
# 에러 페이지
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
2.4 Location 매칭 우선순위
우선순위 (높은 순):
1. = (정확한 일치) location = /path
2. ^~ (우선 접두사) location ^~ /path
3. ~ (정규식, 대소문자 구분) location ~ \.php$
4. ~* (정규식, 대소문자 무시) location ~* \.(jpg|png)$
5. / (접두사 일치) location /path
6. / (기본) location /
3. 리버스 프록시 설정
3.1 기본 리버스 프록시
# snippets/proxy-params.conf
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 "";
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;
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
include snippets/proxy-params.conf;
}
}
3.2 프록시 동작 원리
Client Nginx (Reverse Proxy) Backend
│ │ │
│ GET /api/users │ │
│─────────────────────────────>│ │
│ │ GET /api/users │
│ │ Host: api.example.com │
│ │ X-Real-IP: 203.0.113.1 │
│ │ X-Forwarded-For: 203.0.113.1│
│ │─────────────────────────────>│
│ │ │
│ │ 200 OK │
│ │<─────────────────────────────│
│ 200 OK │ │
│<─────────────────────────────│ │
3.3 경로 재작성(Rewrite)
# /api/v1/users → 백엔드의 /users로 전달
location /api/v1/ {
rewrite ^/api/v1/(.*)$ /$1 break;
proxy_pass http://backend;
include snippets/proxy-params.conf;
}
# 또는 proxy_pass에 URI 포함
location /api/v1/ {
proxy_pass http://backend/; # 슬래시 주의!
include snippets/proxy-params.conf;
}
# 조건부 리다이렉트
location /old-page {
return 301 /new-page;
}
# 정규식 기반 rewrite
rewrite ^/blog/(\d{4})/(\d{2})/(.*)$ /posts/$3?year=$1&month=$2 permanent;
4. 로드 밸런싱
4.1 Upstream 설정
# 기본 Round Robin
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
# Least Connections - 가장 적은 연결 수를 가진 서버로
upstream backend_least {
least_conn;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
# IP Hash - 같은 클라이언트 IP는 같은 서버로 (세션 고정)
upstream backend_iphash {
ip_hash;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
# 가중치(Weight) 기반
upstream backend_weighted {
server 10.0.0.1:8080 weight=5; # 50% 트래픽
server 10.0.0.2:8080 weight=3; # 30% 트래픽
server 10.0.0.3:8080 weight=2; # 20% 트래픽
}
# 고급 설정
upstream backend_advanced {
least_conn;
server 10.0.0.1:8080 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 weight=2 max_fails=3 fail_timeout=30s;
server 10.0.0.3:8080 backup; # 다른 서버 장애 시만 사용
server 10.0.0.4:8080 down; # 일시적으로 비활성화
keepalive 32; # 업스트림 커넥션 풀
}
4.2 로드 밸런싱 알고리즘 비교
| 알고리즘 | 설명 | 장점 | 단점 | 적합한 상황 |
|---|---|---|---|---|
| Round Robin | 순차적 분배 (기본) | 간단, 균등 분배 | 서버 성능 차이 미반영 | 동일 사양 서버 |
| Least Connections | 최소 연결 수 서버로 | 부하 균형 | 새 서버에 집중 가능 | 요청 처리 시간 불균등 |
| IP Hash | 클라이언트 IP 기반 | 세션 고정 | 불균등 분배 가능 | 세션 기반 앱 |
| Weight | 가중치 기반 | 성능 차이 반영 | 수동 관리 | 서버 스펙 차이 |
| Random | 무작위 (Nginx Plus) | 분산 환경 적합 | 예측 불가 | 대규모 클러스터 |
4.3 헬스 체크
# 패시브 헬스 체크 (OSS)
upstream backend {
server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
}
# 액티브 헬스 체크 (Nginx Plus 또는 별도 모듈)
# upstream backend {
# zone backend_zone 64k;
# server 10.0.0.1:8080;
# server 10.0.0.2:8080;
# health_check interval=10 fails=3 passes=2 uri=/health;
# }
5. SSL/TLS 설정
5.1 Let's Encrypt + Certbot
# Certbot 설치 및 인증서 발급
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
# 자동 갱신 확인
sudo certbot renew --dry-run
# crontab에 자동 갱신 추가
# 0 0,12 * * * certbot renew --quiet --post-hook "systemctl reload nginx"
5.2 강화된 SSL 설정
# snippets/ssl-params.conf
# 프로토콜 - TLS 1.2, 1.3만 허용
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 on;
# DH 파라미터
ssl_dhparam /etc/nginx/certs/dhparam.pem;
# SSL 세션 캐시
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/certs/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
# HSTS (Strict Transport Security)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
5.3 SSL Termination 패턴
Client (HTTPS) Nginx (SSL Termination) Backend (HTTP)
│ │ │
│ HTTPS (TLS 1.3) │ │
│─────────────────────────>│ │
│ │ HTTP (plain) │
│ │─────────────────────────────>│
│ │ │
│ │ HTTP Response │
│ │<─────────────────────────────│
│ HTTPS Response │ │
│<─────────────────────────│ │
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
include snippets/ssl-params.conf;
location / {
proxy_pass http://backend;
proxy_set_header X-Forwarded-Proto https;
include snippets/proxy-params.conf;
}
}
6. 캐싱 (Proxy Cache)
6.1 기본 캐시 설정
http {
# 캐시 존 정의
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=my_cache:10m # 10MB 메모리 (키 저장)
max_size=10g # 디스크 최대 크기
inactive=60m # 60분 미사용 시 삭제
use_temp_path=off;
server {
listen 443 ssl http2;
server_name example.com;
# 캐시 활성화
location /api/ {
proxy_cache my_cache;
proxy_cache_valid 200 302 10m; # 200, 302 응답 10분
proxy_cache_valid 404 1m; # 404 응답 1분
proxy_cache_use_stale error timeout updating
http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
# 캐시 키 설정
proxy_cache_key "$scheme$request_method$host$request_uri";
# 캐시 상태 헤더 추가
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
include snippets/proxy-params.conf;
}
# 정적 파일 캐시 (브라우저)
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
root /var/www/static;
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
}
}
6.2 캐시 상태 값
| 상태 | 설명 |
|---|---|
HIT | 캐시에서 응답 제공 |
MISS | 캐시 없음, 백엔드에서 가져옴 |
EXPIRED | 캐시 만료, 백엔드에서 갱신 |
STALE | 만료된 캐시이지만 stale 정책으로 제공 |
UPDATING | stale 응답 제공 중, 백그라운드 갱신 |
REVALIDATED | 백엔드가 304 응답, 기존 캐시 재사용 |
BYPASS | 캐시 무시 설정으로 백엔드 직접 요청 |
6.3 캐시 바이패스와 퍼지
# 특정 조건에서 캐시 무시
location /api/ {
proxy_cache my_cache;
# 쿠키가 있으면 캐시 안 함
proxy_cache_bypass $cookie_session;
proxy_no_cache $cookie_session;
# Cache-Control: no-cache 요청 시 캐시 무시
proxy_cache_bypass $http_cache_control;
# 특정 쿼리 파라미터로 캐시 퍼지
# proxy_cache_purge $purge_method; # Nginx Plus
proxy_pass http://backend;
}
7. Rate Limiting (속도 제한)
7.1 기본 Rate Limiting
http {
# 제한 존 정의
# 10MB 메모리, IP당 초당 10 요청
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# IP당 초당 1 요청 (로그인 보호)
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;
# API 키 기반 제한
limit_req_zone $http_x_api_key zone=apikey_limit:10m rate=100r/s;
server {
# API 엔드포인트 - 버스트 허용
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
# 로그인 - 엄격한 제한
location /auth/login {
limit_req zone=login_limit burst=5;
limit_req_status 429;
proxy_pass http://backend;
}
}
}
7.2 연결 수 제한
http {
# IP당 동시 연결 수 제한
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
# IP당 최대 100 동시 연결
limit_conn conn_limit 100;
# 다운로드 대역폭 제한
location /downloads/ {
limit_conn conn_limit 5; # 5개 동시 다운로드
limit_rate 500k; # 연결당 500KB/s
limit_rate_after 10m; # 처음 10MB는 제한 없음
}
}
}
7.3 Rate Limiting 동작 이해
rate=10r/s, burst=20, nodelay:
시간 │ 요청수 │ 처리 │ 설명
─────│───────│─────│──────────────────────────────
0.0s │ 25 │ 21 │ 10(rate) + 20(burst) = 30까지 허용
│ │ │ 21개 즉시 처리, 4개 429 응답
0.1s │ 5 │ 1 │ burst 버킷에 1개 여유 (0.1s * 10r/s = 1)
1.0s │ 5 │ 5 │ burst 버킷이 10개 회복
8. WebSocket 프록시
8.1 WebSocket 설정
# WebSocket 업그레이드를 위한 map
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream websocket_backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
# WebSocket은 sticky session 필요
ip_hash;
}
server {
listen 443 ssl http2;
server_name ws.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
location /ws/ {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $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;
# WebSocket 타임아웃 (기본 60초)
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
8.2 WebSocket 핸드셰이크 흐름
Client Nginx Backend
│ │ │
│ GET /ws/ HTTP/1.1 │ │
│ Upgrade: websocket │ │
│ Connection: Upgrade │ │
│────────────────────────>│ │
│ │ GET /ws/ HTTP/1.1 │
│ │ Upgrade: websocket │
│ │ Connection: Upgrade │
│ │────────────────────────>│
│ │ │
│ │ 101 Switching Protocols │
│ │<────────────────────────│
│ 101 Switching Protocols │ │
│<────────────────────────│ │
│ │ │
│ ← WebSocket frames → │ ← WebSocket frames → │
9. 보안 설정
9.1 보안 헤더
server {
# XSS 보호
add_header X-XSS-Protection "1; mode=block" always;
# MIME 타입 스니핑 방지
add_header X-Content-Type-Options "nosniff" always;
# 클릭재킹 방지
add_header X-Frame-Options "SAMEORIGIN" always;
# Referrer 정책
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Content Security Policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: cdn.example.com; font-src 'self' fonts.gstatic.com;" always;
# Permissions Policy
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
}
9.2 접근 제어
server {
# IP 기반 접근 제어
location /admin/ {
allow 10.0.0.0/8;
allow 192.168.0.0/16;
deny all;
proxy_pass http://backend;
}
# Basic Authentication
location /internal/ {
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://backend;
}
# 특정 HTTP 메서드만 허용
location /api/ {
limit_except GET POST PUT DELETE {
deny all;
}
proxy_pass http://backend;
}
# 서버 정보 숨기기
server_tokens off;
# 숨겨진 파일 접근 차단
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
9.3 DDoS 방어 기본 설정
http {
# 연결 제한
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=30r/s;
# 요청 본문 크기 제한
client_max_body_size 10m;
client_body_buffer_size 128k;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
# 타임아웃 설정
client_body_timeout 12;
client_header_timeout 12;
send_timeout 10;
server {
limit_conn conn_per_ip 50;
limit_req zone=req_per_ip burst=50 nodelay;
# User-Agent 기반 봇 차단
if ($http_user_agent ~* (bot|crawler|spider|scraper)) {
return 403;
}
# 빈 User-Agent 차단
if ($http_user_agent = "") {
return 403;
}
}
}
10. Gzip 압축과 성능 최적화
10.1 Gzip 상세 설정
http {
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5; # 1-9 (5가 최적 밸런스)
gzip_min_length 1024; # 1KB 이하는 압축 안 함
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/xml+rss
application/atom+xml
image/svg+xml
font/opentype
font/ttf
font/woff
font/woff2;
# 미리 압축된 파일 사용 (빌드 시 생성)
gzip_static on;
}
10.2 Brotli 압축 (Nginx 모듈)
# Brotli는 Gzip보다 20-30% 더 효율적
# 별도 모듈 설치 필요
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
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 image/svg+xml;
brotli_static on;
}
10.3 성능 튜닝 체크리스트
# /etc/nginx/nginx.conf
worker_processes auto; # CPU 코어 수
worker_rlimit_nofile 65535;
events {
worker_connections 10240;
multi_accept on;
use epoll;
}
http {
# 파일 전송 최적화
sendfile on;
tcp_nopush on; # sendfile과 함께 사용
tcp_nodelay on; # keepalive에서 유효
# 타임아웃
keepalive_timeout 65;
keepalive_requests 1000;
# 버퍼
client_body_buffer_size 16k;
client_header_buffer_size 1k;
client_max_body_size 50m;
large_client_header_buffers 4 8k;
# 파일 캐시
open_file_cache max=10000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# 업스트림 커넥션 풀
upstream backend {
keepalive 32;
keepalive_requests 100;
keepalive_timeout 60s;
}
}
11. Docker & Kubernetes 연동
11.1 Docker Compose로 Nginx 구성
# docker-compose.yml
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/certs:/etc/nginx/certs:ro
- ./nginx/cache:/var/cache/nginx
depends_on:
- app1
- app2
networks:
- webnet
restart: unless-stopped
deploy:
resources:
limits:
cpus: '2'
memory: 512M
app1:
image: myapp:latest
expose:
- "3000"
networks:
- webnet
app2:
image: myapp:latest
expose:
- "3000"
networks:
- webnet
networks:
webnet:
driver: bridge
# nginx/conf.d/default.conf
upstream app {
server app1:3000;
server app2:3000;
keepalive 16;
}
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
location / {
proxy_pass http://app;
include /etc/nginx/snippets/proxy-params.conf;
}
}
11.2 Kubernetes Ingress (Nginx Ingress Controller)
# Nginx Ingress Controller 설치
# kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.0/deploy/static/provider/cloud/deploy.yaml
# Ingress 리소스 정의
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/rate-limit: "10"
nginx.ingress.kubernetes.io/rate-limit-window: "1m"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
- api.example.com
secretName: example-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-svc
port:
number: 80
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backend-svc
port:
number: 8080
12. Nginx vs Traefik vs Caddy 비교
| 기능 | Nginx | Traefik | Caddy |
|---|---|---|---|
| 설정 방식 | 파일 기반 | 동적 (Docker Label, K8s) | Caddyfile / JSON API |
| 자동 HTTPS | 수동 (Certbot) | 내장 (ACME) | 내장 (ACME) |
| 서비스 디스커버리 | 수동 | Docker/K8s 자동 | 제한적 |
| 성능 | 최고 수준 | 좋음 | 좋음 |
| HTTP/3 | 모듈로 지원 | 내장 | 내장 |
| 대시보드 | 없음 (Nginx Plus 유료) | 내장 웹 UI | 내장 API |
| 설정 리로드 | nginx -s reload | 자동 핫 리로드 | 자동 핫 리로드 |
| 커뮤니티 | 매우 큼 | 성장 중 | 성장 중 |
| 적합 환경 | 범용, 고성능 | 마이크로서비스, Docker | 소규모, 간편 설정 |
| 라이선스 | BSD | MIT | Apache 2.0 |
12.1 선택 가이드
Nginx를 선택해야 할 때:
- 최고의 성능과 안정성이 필요할 때
- 복잡한 리버스 프록시 규칙이 필요할 때
- 레거시 시스템이나 정적 파일 서빙에 최적
- 큰 커뮤니티와 풍부한 문서가 필요할 때
Traefik을 선택해야 할 때:
- Docker/Kubernetes 환경에서 자동 서비스 디스커버리가 필요할 때
- 동적으로 라우팅 규칙이 변경되는 마이크로서비스 환경
- 내장 대시보드와 메트릭이 필요할 때
Caddy를 선택해야 할 때:
- 자동 HTTPS가 가장 중요할 때
- 간단한 설정으로 빠르게 시작하고 싶을 때
- 소규모 프로젝트나 개발 환경
13. 면접 대비 퀴즈
Q1. Nginx의 이벤트 드리븐 아키텍처가 Apache의 프로세스 모델보다 유리한 이유는?
Apache의 전통적인 Prefork/Worker MPM은 각 연결마다 프로세스나 스레드를 할당합니다. 10,000개의 동시 연결이면 10,000개의 프로세스/스레드가 필요하며, 각각 수 MB의 메모리를 소비합니다.
Nginx는 이벤트 루프 기반으로 동작하여 소수의 Worker Process(보통 CPU 코어 수)가 epoll/kqueue 같은 OS 이벤트 메커니즘을 통해 수만 개의 연결을 비동기적으로 처리합니다.
핵심 차이:
- 메모리 효율성: Nginx는 연결당 수 KB vs Apache는 연결당 수 MB
- 컨텍스트 스위칭: Nginx는 최소화 vs Apache는 프로세스/스레드 전환 오버헤드
- C10K 문제: Nginx는 설계 단계부터 이 문제를 해결하기 위해 만들어짐
- 단, CPU 집약적인 작업에는 이벤트 모델이 불리할 수 있음
Q2. proxy_pass에서 URI 슬래시(/)의 유무에 따른 차이는?
이것은 Nginx 설정에서 가장 흔한 실수 중 하나입니다.
proxy_pass http://backend; (슬래시 없음):
요청 URI가 그대로 전달됩니다. /api/users 요청은 백엔드에 /api/users로 전달됩니다.
proxy_pass http://backend/; (슬래시 있음):
location에 매칭된 부분이 제거되고 나머지가 전달됩니다.
예시 - location /api/에서:
- 슬래시 없음:
/api/users요청은http://backend/api/users로 전달 - 슬래시 있음:
/api/users요청은http://backend/users로 전달
이 차이를 이해하지 못하면 404 에러나 잘못된 라우팅이 발생합니다.
Q3. Nginx에서 Rate Limiting의 burst와 nodelay 옵션의 역할은?
limit_req zone=api rate=10r/s burst=20 nodelay;
rate=10r/s: 초당 10개 요청까지 허용합니다. 내부적으로 100ms마다 1개 요청을 허용하는 토큰 버킷 방식입니다.
burst=20: rate를 초과하는 요청을 최대 20개까지 큐에 대기시킵니다. burst 없이는 rate 초과 시 즉시 429 응답입니다.
nodelay: burst 큐에 들어온 요청을 지연 없이 즉시 처리합니다. nodelay 없이는 요청이 rate에 맞춰 순차적으로 처리되므로 대기 시간이 발생합니다.
조합 효과:
rate=10r/s burst=20: 순간 최대 21개 처리 가능하지만, burst 요청은 지연됨rate=10r/s burst=20 nodelay: 순간 최대 21개를 즉시 처리, 초과분은 429
Q4. SSL Termination이란 무엇이고 왜 사용하나요?
SSL Termination(SSL 종료)은 HTTPS 암호화/복호화를 Nginx(리버스 프록시)에서 처리하고, 백엔드 서버에는 일반 HTTP로 통신하는 패턴입니다.
장점:
- 백엔드 부하 감소: SSL 핸드셰이크와 암복호화는 CPU 집약적 작업. 이를 Nginx에 집중시킴
- 인증서 관리 중앙화: 모든 인증서를 Nginx 한 곳에서 관리
- 백엔드 단순화: 백엔드 애플리케이션이 SSL을 신경 쓰지 않아도 됨
- 성능 최적화: Nginx의 SSL 세션 캐시, OCSP Stapling 등 활용
보안 고려사항:
- Nginx와 백엔드 사이의 내부 네트워크가 안전해야 함
- 필요시 내부 통신에도 mTLS(Mutual TLS) 적용 가능
X-Forwarded-Proto헤더로 백엔드에 원본 프로토콜 전달
Q5. Nginx에서 upstream keepalive의 역할과 적절한 값은?
keepalive는 Nginx와 업스트림(백엔드) 서버 간의 유휴(idle) 연결을 캐시하는 커넥션 풀입니다.
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
keepalive 32;
}
keepalive가 없으면: 매 요청마다 TCP 3-way handshake가 발생합니다. 고부하 시 TIME_WAIT 상태의 소켓이 급증하여 포트 고갈이 발생할 수 있습니다.
keepalive가 있으면: 이전 연결을 재사용하여 TCP 핸드셰이크 오버헤드를 제거합니다. 지연 시간 감소와 처리량 증가 효과가 있습니다.
적절한 값:
- 동시 연결 수의 약 2배가 시작점
- 너무 높으면 메모리 낭비, 너무 낮으면 커넥션 재사용 효과 감소
proxy_http_version 1.1;과proxy_set_header Connection "";을 함께 설정해야 작동