1. Nginx 아키텍처와 설정 구조
1.1 이벤트 기반 아키텍처: Master-Worker 모델
Nginx는 Apache httpd의 프로세스/스레드 기반 모델과 근본적으로 다른 **이벤트 기반(Event-Driven) 아키텍처**를 채택했다. 이 설계 철학이 Nginx가 단일 서버에서 **수십만 개의 동시 접속**을 처리할 수 있는 핵심 이유다.
┌─────────────────────────────────────────────────────────┐
│ Master Process │
│ - 설정 파일 읽기 및 검증 │
│ - Worker 프로세스 생성/관리 (fork) │
│ - 포트 바인딩 (80, 443) │
│ - 시그널 처리 (reload, stop, reopen) │
└─────────┬───────────┬───────────┬───────────┬───────────┘
│ │ │ │
┌─────▼─────┐ ┌───▼───┐ ┌───▼───┐ ┌───▼───┐
│ Worker 0 │ │Worker 1│ │Worker 2│ │Worker 3│
│ Event Loop │ │ ... │ │ ... │ │ ... │
│ epoll/kq │ │ │ │ │ │ │
│ 수천 conn │ │ │ │ │ │ │
└───────────┘ └────────┘ └────────┘ └────────┘
**Master Process**는 root 권한으로 실행되며 설정 파일 파싱, 포트 바인딩, Worker 프로세스 관리를 담당한다. `fork()` 시스템 콜로 Worker를 생성하며, 설정 리로드 시 기존 연결을 끊지 않고 새로운 Worker를 띄운 뒤 기존 Worker를 graceful shutdown한다.
**Worker Process**는 실제 클라이언트 요청을 처리하는 핵심 단위다. 각 Worker는 독립적인 **이벤트 루프**를 운영하며, OS의 I/O 다중화 메커니즘(`epoll` on Linux, `kqueue` on FreeBSD/macOS)을 활용해 블로킹 없이 수천 개의 연결을 동시에 처리한다. 커넥션마다 스레드를 할당하는 방식 대비 컨텍스트 스위칭과 메모리 오버헤드가 극적으로 줄어든다.
1.2 nginx.conf 설정 컨텍스트 구조
Nginx 설정은 **계층적 컨텍스트(Context)** 구조를 따른다. 자식 컨텍스트는 부모의 설정을 **상속**하며, 동일한 디렉티브를 자식에서 재선언하면 **오버라이드**된다.
============================================
Main Context (전역 설정)
============================================
user nginx; # Worker 프로세스 실행 사용자
worker_processes auto; # CPU 코어 수만큼 Worker 생성
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
============================================
Events Context (연결 처리 설정)
============================================
events {
worker_connections 1024; # Worker당 최대 동시 연결 수
multi_accept on; # 한 번에 여러 연결 수락
use epoll; # Linux에서 epoll 사용 (기본값)
}
============================================
HTTP Context (HTTP 프로토콜 설정)
============================================
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
========================================
Server Context (가상 호스트)
========================================
server {
listen 80;
server_name example.com;
====================================
Location Context (URL 경로 매칭)
====================================
location / {
root /var/www/html;
index index.html;
}
location /api/ {
proxy_pass http://backend;
}
}
}
============================================
Stream Context (TCP/UDP 프록시, L4)
============================================
stream {
server {
listen 3306;
proxy_pass mysql_backend;
}
}
**컨텍스트 계층 요약:**
| 컨텍스트 | 위치 | 역할 |
| ------------ | ----------- | ---------------------------------------- |
| **Main** | 최상위 | 전역 설정 (user, worker, pid, error_log) |
| **Events** | Main 내부 | 연결 처리 메커니즘 (worker_connections) |
| **HTTP** | Main 내부 | HTTP 프로토콜 관련 전체 설정 |
| **Server** | HTTP 내부 | 가상 호스트 (도메인별 설정) |
| **Location** | Server 내부 | URL 경로별 요청 처리 규칙 |
| **Upstream** | HTTP 내부 | 백엔드 서버 그룹 (로드 밸런싱) |
| **Stream** | Main 내부 | TCP/UDP L4 프록시 |
1.3 설정 파일 구조화 Best Practice
프로덕션 환경에서는 단일 `nginx.conf`에 모든 설정을 넣지 않고, 모듈화하여 관리한다.
/etc/nginx/
├── nginx.conf # 메인 설정 (include로 분리)
├── conf.d/ # 공통 설정 스니펫
│ ├── ssl-params.conf # SSL/TLS 공통 파라미터
│ ├── proxy-params.conf # 리버스 프록시 공통 헤더
│ ├── security-headers.conf # 보안 헤더
│ └── gzip.conf # 압축 설정
├── sites-available/ # 사이트별 설정 파일
│ ├── example.com.conf
│ ├── api.example.com.conf
│ └── admin.example.com.conf
├── sites-enabled/ # 활성화된 사이트 (심볼릭 링크)
│ ├── example.com.conf -> ../sites-available/example.com.conf
│ └── api.example.com.conf -> ../sites-available/api.example.com.conf
└── snippets/ # 재사용 가능한 설정 조각
├── letsencrypt.conf
└── fastcgi-php.conf
nginx.conf 메인 파일
http {
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*.conf;
}
2. Virtual Host / Server Block 설정
Nginx의 **Server Block**은 Apache의 Virtual Host에 해당하며, 하나의 서버에서 여러 도메인을 호스팅할 수 있게 한다.
2.1 기본 Server Block 설정
/etc/nginx/sites-available/example.com.conf
── 기본 도메인 ──
server {
listen 80;
listen [::]:80; # IPv6 지원
server_name example.com www.example.com;
root /var/www/example.com/html;
index index.html index.htm;
접근 로그를 도메인별로 분리
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
location / {
try_files $uri $uri/ =404;
}
}
── 두 번째 도메인 ──
server {
listen 80;
listen [::]:80;
server_name blog.example.com;
root /var/www/blog.example.com/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
2.2 Default Server (catch-all)
정의되지 않은 도메인으로 들어오는 요청을 처리하는 기본 서버 블록이다. 보안을 위해 444(연결 끊기)로 응답하는 것이 권장된다.
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _; # 모든 미매칭 도메인
정의되지 않은 호스트 요청은 즉시 연결 종료
return 444;
}
2.3 Server Name 매칭 우선순위
Nginx는 `server_name` 매칭 시 다음 우선순위를 따른다:
1. **정확한 이름**: `server_name example.com`
2. **앞쪽 와일드카드**: `server_name *.example.com`
3. **뒤쪽 와일드카드**: `server_name example.*`
4. **정규표현식**: `server_name ~^(?<subdomain>.+)\.example\.com$`
5. **default_server**: 위 모두 미매칭 시
정규표현식으로 서브도메인 캡처
server {
listen 80;
server_name ~^(?<subdomain>.+)\.example\.com$;
location / {
root /var/www/$subdomain;
}
}
2.4 사이트 활성화/비활성화
사이트 활성화
sudo ln -s /etc/nginx/sites-available/example.com.conf \
/etc/nginx/sites-enabled/example.com.conf
설정 검증
sudo nginx -t
리로드 (무중단)
sudo systemctl reload nginx
3. 리버스 프록시 설정
3.1 기본 Reverse Proxy
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1: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_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
}
**각 헤더의 역할:**
| 헤더 | 목적 |
| ------------------- | ------------------------------------------------------- |
| `Host` | 원본 요청의 Host 헤더 전달 |
| `X-Real-IP` | 실제 클라이언트 IP (프록시 뒤에서 원본 IP 식별) |
| `X-Forwarded-For` | 프록시 체인을 거치며 누적되는 클라이언트 IP 목록 |
| `X-Forwarded-Proto` | 원본 프로토콜 (http/https) — 백엔드에서 리다이렉트 판단 |
| `X-Forwarded-Host` | 원본 Host 헤더 |
| `X-Forwarded-Port` | 원본 포트 |
3.2 재사용 가능한 프록시 파라미터 스니펫
/etc/nginx/conf.d/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_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_http_version 1.1;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
Server Block에서 include로 재사용
location / {
proxy_pass http://backend;
include /etc/nginx/conf.d/proxy-params.conf;
}
3.3 WebSocket 프록시
WebSocket은 HTTP Upgrade 메커니즘을 사용하므로, `Upgrade`와 `Connection` 홉별(hop-by-hop) 헤더를 명시적으로 전달해야 한다. Nginx는 기본적으로 이 헤더들을 포워딩하지 않는다.
── map으로 Connection 헤더를 동적 설정 ──
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name ws.example.com;
location /ws/ {
proxy_pass http://127.0.0.1:8080;
WebSocket 필수 설정
proxy_http_version 1.1; # HTTP/1.1 필수 (Upgrade 지원)
proxy_set_header Upgrade $http_upgrade; # 클라이언트의 Upgrade 헤더 전달
proxy_set_header Connection $connection_upgrade; # 동적 Connection 헤더
일반 프록시 헤더
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은 장시간 연결이므로 타임아웃 연장
proxy_read_timeout 86400s; # 24시간 (기본 60초는 idle 연결 끊김)
proxy_send_timeout 86400s;
}
}
3.4 경로 기반 라우팅 (마이크로서비스)
server {
listen 80;
server_name api.example.com;
사용자 서비스
location /api/users/ {
proxy_pass http://user-service:3001/; # 끝에 / 주의: /api/users/ 제거 후 전달
include /etc/nginx/conf.d/proxy-params.conf;
}
주문 서비스
location /api/orders/ {
proxy_pass http://order-service:3002/;
include /etc/nginx/conf.d/proxy-params.conf;
}
결제 서비스
location /api/payments/ {
proxy_pass http://payment-service:3003/;
include /etc/nginx/conf.d/proxy-params.conf;
proxy_read_timeout 120s; # 결제는 타임아웃 연장
}
}
> **주의:** `proxy_pass` URL 끝에 `/`가 있으면 `location`에 매칭된 부분이 제거된다. `/api/users/123` 요청이 `http://user-service:3001/123`으로 전달된다. `/`가 없으면 전체 URI가 그대로 전달된다.
4. 로드 밸런싱
4.1 Upstream 블록과 알고리즘
============================================
1. Round Robin (기본값)
요청을 순차적으로 분배
============================================
upstream backend_roundrobin {
server 10.0.0.1:8080; # 기본 가중치 1
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
============================================
2. Weighted Round Robin (가중 라운드 로빈)
서버 성능에 따라 비율 분배
============================================
upstream backend_weighted {
server 10.0.0.1:8080 weight=5; # 요청의 5/8
server 10.0.0.2:8080 weight=2; # 요청의 2/8
server 10.0.0.3:8080 weight=1; # 요청의 1/8
}
============================================
3. Least Connections (최소 연결)
활성 연결이 가장 적은 서버로 전달
============================================
upstream backend_leastconn {
least_conn;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
============================================
4. 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;
}
============================================
5. Generic Hash (커스텀 해시)
임의 변수 기반 해시
============================================
upstream backend_hash {
hash $request_uri consistent; # URI 기반 + 일관적 해싱
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
**알고리즘 선택 가이드:**
| 알고리즘 | 적합한 상황 | 주의점 |
| --------------------- | ----------------------------------- | ----------------------------------- |
| **Round Robin** | 무상태 서비스, 동일 스펙 서버 | 서버 스펙 차이 시 불균형 |
| **Weighted** | 서버 스펙이 다른 경우 | 가중치 수동 관리 필요 |
| **Least Connections** | 요청 처리 시간 편차가 큰 경우 | 짧은 요청 위주면 Round Robin과 유사 |
| **IP Hash** | 세션 고정이 필요한 경우 (레거시 앱) | 서버 추가/제거 시 재분배 발생 |
| **Generic Hash** | 캐시 최적화 (동일 URI → 동일 서버) | consistent 해싱 권장 |
4.2 서버 상태 및 백업
upstream backend {
least_conn;
server 10.0.0.1:8080; # 정상 서버
server 10.0.0.2:8080; # 정상 서버
server 10.0.0.3:8080 backup; # 백업: 위 서버 모두 다운 시 활성화
server 10.0.0.4:8080 down; # 비활성화 (유지보수)
server 10.0.0.5:8080 max_fails=3 fail_timeout=30s;
max_fails=3: 30초 내 3회 실패 시 비정상 판정
fail_timeout=30s: 비정상 판정 후 30초간 요청 제외
}
4.3 Keepalive 커넥션 풀
백엔드 서버와의 TCP 연결을 재사용하여 연결 설정/해제 오버헤드를 줄인다.
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
keepalive 32; # 각 Worker당 유지할 유휴 커넥션 수
keepalive_requests 1000; # 하나의 커넥션으로 처리할 최대 요청 수
keepalive_timeout 60s; # 유휴 커넥션 유지 시간
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1; # keepalive는 HTTP/1.1 필수
proxy_set_header Connection ""; # "close" 대신 빈 값으로 keepalive 활성화
}
}
5. SSL/TLS 설정
5.1 기본 HTTPS 설정
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
── 인증서 ──
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
── 프로토콜 ──
ssl_protocols TLSv1.2 TLSv1.3; # TLS 1.0, 1.1 비활성화
── Cipher Suites (TLS 1.2용) ──
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off; # TLS 1.3에서는 off 권장
── Elliptic Curves ──
ssl_ecdh_curve X25519:secp384r1:secp256r1;
── 세션 재사용 ──
ssl_session_cache shared:SSL:10m; # 10MB = ~40,000 세션
ssl_session_timeout 1d; # 세션 유효 시간
ssl_session_tickets off; # Forward Secrecy 보장
root /var/www/example.com/html;
index index.html;
}
5.2 HTTP → HTTPS 리다이렉트
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
모든 HTTP 요청을 HTTPS로 301 리다이렉트
return 301 https://$host$request_uri;
}
5.3 HSTS (HTTP Strict Transport Security)
HTTPS server block 내에 추가
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
- **`max-age=63072000`**: 2년간 HTTPS만 사용 (최소 1년 권장)
- **`includeSubDomains`**: 모든 서브도메인에도 적용
- **`preload`**: 브라우저의 HSTS Preload 목록 등록 자격
- **`always`**: 오류 응답(4xx, 5xx)에서도 헤더 전송
5.4 OCSP Stapling
OCSP Stapling은 인증서 유효성 검증을 서버가 대행하여 클라이언트의 CA 직접 조회를 제거한다. 초기 연결 속도가 개선되고 개인정보가 보호된다.
ssl_stapling on;
ssl_stapling_verify on;
OCSP 응답 검증용 신뢰 체인 (Let's Encrypt 중간 인증서 포함)
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
OCSP 응답서버 조회용 DNS resolver
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
5.5 프로덕션 SSL 통합 스니펫
/etc/nginx/conf.d/ssl-params.conf
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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_ecdh_curve X25519:secp384r1:secp256r1;
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 8.8.4.4 valid=300s;
resolver_timeout 5s;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
Server Block에서 include
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/nginx/conf.d/ssl-params.conf;
... 나머지 설정
}
5.6 Let's Encrypt 자동 갱신 (Certbot)
인증서 발급
sudo certbot --nginx -d example.com -d www.example.com
자동 갱신 테스트
sudo certbot renew --dry-run
Cron 자동 갱신 (이미 certbot이 설정하지만 명시적으로)
echo "0 0,12 * * * root certbot renew --quiet --deploy-hook 'systemctl reload nginx'" \
| sudo tee /etc/cron.d/certbot-renew
6. 캐싱 설정
6.1 Proxy Cache (리버스 프록시 캐시)
── HTTP Context에서 캐시 경로 정의 ──
proxy_cache_path /var/cache/nginx/proxy
levels=1:2 # 2단계 디렉토리 구조 (파일 분산)
keys_zone=proxy_cache:10m # 캐시 키 저장용 공유 메모리 (1MB ≈ 8,000 키)
max_size=1g # 디스크 캐시 최대 크기
inactive=60m # 60분간 미사용 캐시 삭제
use_temp_path=off; # 임시 파일 경로 미사용 (직접 기록 → 성능 향상)
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend;
proxy_cache proxy_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; # 백엔드 오류 시 stale 캐시 제공
proxy_cache_lock on; # 동시 요청 시 하나만 백엔드로
proxy_cache_min_uses 2; # 2회 이상 요청된 URL만 캐싱
디버깅용 캐시 상태 헤더
add_header X-Cache-Status $upstream_cache_status;
}
캐시 바이패스 (관리자용)
location /api/ {
proxy_pass http://backend;
proxy_cache proxy_cache;
Cookie에 nocache가 있거나, 헤더에 Cache-Control: no-cache 시 바이패스
proxy_cache_bypass $http_cache_control;
proxy_no_cache $cookie_nocache;
}
}
**`X-Cache-Status` 값:**
| 상태 | 의미 |
| ---------- | ------------------------------ |
| `HIT` | 캐시에서 직접 제공 |
| `MISS` | 캐시 없음 → 백엔드 요청 |
| `EXPIRED` | 만료된 캐시 → 백엔드 재요청 |
| `STALE` | 만료됐지만 stale 정책으로 제공 |
| `UPDATING` | 갱신 중에 stale 캐시 제공 |
| `BYPASS` | 캐시 바이패스됨 |
6.2 FastCGI Cache (PHP 등)
fastcgi_cache_path /var/cache/nginx/fastcgi
levels=1:2
keys_zone=fastcgi_cache:10m
max_size=512m
inactive=30m;
server {
listen 80;
server_name wordpress.example.com;
캐시 바이패스 조건 정의
set $skip_cache 0;
POST 요청은 캐시하지 않음
if ($request_method = POST) {
set $skip_cache 1;
}
쿼리스트링이 있으면 캐시하지 않음
if ($query_string != "") {
set $skip_cache 1;
}
WordPress 관리자 페이지 캐시 제외
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php") {
set $skip_cache 1;
}
로그인 사용자 캐시 제외
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
set $skip_cache 1;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_cache fastcgi_cache;
fastcgi_cache_valid 200 30m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache_use_stale error timeout updating http_500;
add_header X-FastCGI-Cache $upstream_cache_status;
}
}
6.3 브라우저 캐싱 (정적 리소스)
── 정적 파일에 대한 장기 브라우저 캐시 ──
location ~* \.(jpg|jpeg|png|gif|ico|webp|avif|svg)$ {
expires 30d; # Expires 헤더 설정
add_header Cache-Control "public, immutable"; # 브라우저가 재검증 없이 캐시 사용
access_log off; # 정적 파일 로그 비활성화 (I/O 절감)
}
location ~* \.(css|js)$ {
expires 7d;
add_header Cache-Control "public";
}
location ~* \.(woff|woff2|ttf|eot)$ {
expires 365d;
add_header Cache-Control "public, immutable";
add_header Access-Control-Allow-Origin "*"; # 폰트 CORS
}
── HTML은 짧은 캐시 또는 no-cache ──
location ~* \.html$ {
expires -1; # no-cache
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
7. Rate Limiting과 Connection Limiting
7.1 Rate Limiting (요청 속도 제한)
Nginx의 Rate Limiting은 **Leaky Bucket 알고리즘**을 사용한다. 요청은 버킷(zone)에 들어가고, 설정된 속도(rate)로 처리된다.
── HTTP Context에서 Zone 정의 ──
키: 클라이언트 IP
공유 메모리: 10MB (약 160,000 IP 주소 저장)
속도: 초당 10개 요청
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
로그인 엔드포인트: 초당 1개 (Brute Force 방지)
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
API 엔드포인트: 초당 50개
limit_req_zone $binary_remote_addr zone=api:10m rate=50r/s;
Rate Limit 초과 시 로그 레벨
limit_req_status 429; # 기본 503 대신 429 Too Many Requests
limit_req_log_level warn;
server {
listen 80;
server_name example.com;
── 일반 페이지 ──
location / {
limit_req zone=general burst=20 nodelay;
burst=20: 순간 20개까지 초과 허용
nodelay: burst 범위 내 요청을 지연 없이 즉시 처리
proxy_pass http://backend;
}
── 로그인 페이지 ──
location /login {
limit_req zone=login burst=5 nodelay;
proxy_pass http://backend;
}
── API ──
location /api/ {
limit_req zone=api burst=100 nodelay;
proxy_pass http://backend;
}
}
**burst와 nodelay 동작 원리:**
Rate: 10r/s, Burst: 20
시간 0에 30개 요청 도착:
├── 10개: 즉시 처리 (rate 범위)
├── 20개: burst 큐에 저장
│ nodelay 없으면: 100ms 간격으로 처리 (2초간)
│ nodelay 있으면: 즉시 처리 (큐 슬롯만 점유)
└── 나머지: 429 거부
7.2 Connection Limiting (동시 연결 제한)
클라이언트 IP당 동시 연결 수 제한
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
서버 전체 동시 연결 수 제한
limit_conn_zone $server_name zone=conn_per_server:10m;
limit_conn_status 429;
limit_conn_log_level warn;
server {
listen 80;
server_name example.com;
IP당 최대 100개 동시 연결
limit_conn conn_per_ip 100;
서버당 최대 10,000개 동시 연결
limit_conn conn_per_server 10000;
다운로드 대역폭 제한 (선택)
location /downloads/ {
limit_conn conn_per_ip 5; # 다운로드는 IP당 5개로 제한
limit_rate 500k; # 연결당 500KB/s 속도 제한
limit_rate_after 10m; # 첫 10MB는 제한 없이, 이후 제한
}
}
7.3 IP 화이트리스트와 Rate Limiting 결합
내부 네트워크는 Rate Limiting 면제
geo $limit {
default 1;
10.0.0.0/8 0; # 내부 네트워크
192.168.0.0/16 0; # 내부 네트워크
172.16.0.0/12 0; # 내부 네트워크
}
map $limit $limit_key {
0 ""; # 빈 키 → Rate Limiting 적용 안 됨
1 $binary_remote_addr; # 외부 IP → Rate Limiting 적용
}
limit_req_zone $limit_key zone=api:10m rate=10r/s;
8. Gzip/Brotli 압축
8.1 Gzip 압축 설정
/etc/nginx/conf.d/gzip.conf
gzip on;
gzip_vary on; # Vary: Accept-Encoding 헤더 추가
gzip_proxied any; # 프록시 응답도 압축
gzip_comp_level 6; # 압축 레벨 (1-9, 6이 성능/압축 균형점)
gzip_min_length 1000; # 1KB 미만 파일은 압축 효과 없음 → 제외
gzip_buffers 16 8k; # 압축 버퍼
gzip_types
text/plain
text/css
text/javascript
text/xml
application/javascript
application/json
application/xml
application/rss+xml
application/atom+xml
application/vnd.ms-fontobject
font/opentype
font/ttf
image/svg+xml
image/x-icon;
이미 압축된 파일 제외 (이미지, 동영상 등은 gzip_types에 미포함)
gzip_disable "msie6"; # IE6 비활성화 (레거시)
8.2 Brotli 압축 설정
Brotli는 Gzip 대비 **15-25% 더 높은 압축률**을 제공한다. 모던 브라우저 대부분이 지원하며, Nginx에서 `ngx_brotli` 모듈이 필요하다.
Brotli 동적 압축
brotli on;
brotli_comp_level 6; # 동적 압축은 6 권장 (11은 CPU 과부하)
brotli_min_length 1000;
brotli_types
text/plain
text/css
text/javascript
text/xml
application/javascript
application/json
application/xml
application/rss+xml
font/opentype
font/ttf
image/svg+xml;
Brotli 정적 압축 (미리 압축된 .br 파일 제공)
brotli_static on;
8.3 Dual Compression 전략
Brotli를 지원하는 브라우저에는 Brotli로, 지원하지 않는 브라우저에는 Gzip으로 폴백한다.
빌드 시 정적 파일 사전 압축 (CI/CD 파이프라인)
gzip -k -9 dist/**/*.{js,css,html,json,svg}
brotli -k -q 11 dist/**/*.{js,css,html,json,svg}
Nginx 설정
brotli_static on; # .br 파일이 있으면 우선 제공
gzip_static on; # .gz 파일이 있으면 제공 (Brotli 미지원 시)
gzip on; # 사전 압축 파일 없으면 동적 gzip
**압축 성능 비교:**
| 알고리즘 | 압축률 (일반 JS) | CPU 부하 (동적) | 브라우저 지원 |
| ---------- | ---------------- | ---------------- | ------------- |
| Gzip L6 | 70-75% | 낮음 | 99%+ |
| Brotli L6 | 75-80% | 중간 | 96%+ |
| Brotli L11 | 80-85% | 높음 (정적 전용) | 96%+ |
9. 보안 헤더
9.1 종합 보안 헤더 설정
/etc/nginx/conf.d/security-headers.conf
── Clickjacking 방지 ──
add_header X-Frame-Options "DENY" always;
DENY: 어떤 사이트도 iframe으로 삽입 불가
SAMEORIGIN: 같은 도메인만 iframe 허용
참고: CSP frame-ancestors가 더 현대적인 대안
── MIME 타입 스니핑 방지 ──
add_header X-Content-Type-Options "nosniff" always;
브라우저가 Content-Type을 무시하고 자체 판단하는 것을 방지
── XSS 필터 (레거시, 현대 브라우저는 CSP 사용) ──
add_header X-XSS-Protection "0" always;
최신 권장: "0" (비활성화) — CSP가 더 안전하고 정확함
── Referrer 정보 제어 ──
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
같은 도메인: 전체 URL 전송
다른 도메인: origin(도메인)만 전송
HTTP→HTTPS 다운그레이드: 전송 안 함
── 권한 정책 (카메라, 마이크, 위치 등 제어) ──
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
모든 기능 비활성화 (필요한 것만 허용)
── HSTS ──
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
── Cross-Origin 정책 ──
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
9.2 Content Security Policy (CSP)
CSP는 가장 강력한 보안 헤더지만 설정이 복잡하다. **Report-Only 모드로 시작**하여 위반 사항을 모니터링한 후 점진적으로 적용하는 것이 권장된다.
── 1단계: Report-Only 모드 (차단 없이 위반만 보고) ──
add_header Content-Security-Policy-Report-Only
"default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
report-uri /csp-report;" always;
── 2단계: 실제 적용 (위반 시 차단) ──
add_header Content-Security-Policy
"default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';" always;
9.3 Server Block에서 통합 적용
server {
listen 443 ssl http2;
server_name example.com;
include /etc/nginx/conf.d/ssl-params.conf;
include /etc/nginx/conf.d/security-headers.conf;
보안 관련 추가 설정
server_tokens off; # Nginx 버전 정보 숨김
more_clear_headers Server; # Server 헤더 제거 (headers-more 모듈)
...
}
10. 접근 제어 (Access Control)
10.1 IP 기반 접근 제어
── 관리자 페이지: 특정 IP만 허용 ──
location /admin/ {
allow 10.0.0.0/8; # 내부 네트워크
allow 203.0.113.50; # 특정 관리자 IP
deny all; # 나머지 모두 거부
proxy_pass http://backend;
}
── 특정 IP 차단 ──
location / {
deny 192.168.1.100; # 특정 IP 차단
deny 10.0.0.0/24; # 서브넷 차단
allow all; # 나머지 허용
주의: allow/deny 순서가 중요! 먼저 매칭되는 규칙이 적용됨
proxy_pass http://backend;
}
10.2 HTTP Basic Authentication
htpasswd 파일 생성
sudo apt install apache2-utils # Debian/Ubuntu
sudo yum install httpd-tools # RHEL/CentOS
사용자 생성 (-c: 파일 새로 생성, -B: bcrypt 해시)
sudo htpasswd -cB /etc/nginx/.htpasswd admin
추가 사용자
sudo htpasswd -B /etc/nginx/.htpasswd developer
── 특정 경로에 Basic Auth 적용 ──
location /admin/ {
auth_basic "Administrator Area"; # 인증 프롬프트 메시지
auth_basic_user_file /etc/nginx/.htpasswd; # 비밀번호 파일
proxy_pass http://backend;
}
── 특정 경로만 인증 면제 ──
location /admin/health {
auth_basic off; # 헬스체크는 인증 면제
proxy_pass http://backend;
}
10.3 IP + Auth 결합 (satisfy 디렉티브)
location /admin/ {
satisfy any → IP 허용 OR 인증 성공 중 하나만 충족하면 접근 허용
satisfy all → IP 허용 AND 인증 성공 모두 충족해야 접근 허용
satisfy any;
IP 화이트리스트
allow 10.0.0.0/8;
deny all;
Basic Auth (IP 화이트리스트 밖에서 접근 시)
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://backend;
}
10.4 GeoIP 기반 접근 제어
GeoIP2 모듈 필요 (ngx_http_geoip2_module)
geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
auto_reload 60m;
$geoip2_data_country_code country iso_code;
}
특정 국가 차단
map $geoip2_data_country_code $allowed_country {
default yes;
CN no; # 중국
RU no; # 러시아
}
server {
if ($allowed_country = no) {
return 403;
}
}
11. 로깅 설정
11.1 기본 로그 설정
── Access Log ──
기본 log_format: combined
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log combined;
── Error Log ──
레벨: debug, info, notice, warn, error, crit, alert, emerg
error_log /var/log/nginx/error.log warn;
11.2 JSON 로그 포맷 (로그 분석 도구 연동)
JSON 형태의 구조화 로깅은 Elasticsearch, Datadog, Splunk 같은 분석 도구와의 연동에 필수적이다.
log_format json_combined escape=json
'{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"remote_user":"$remote_user",'
'"request_method":"$request_method",'
'"request_uri":"$request_uri",'
'"server_protocol":"$server_protocol",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for",'
'"upstream_addr":"$upstream_addr",'
'"upstream_status":"$upstream_status",'
'"upstream_response_time":"$upstream_response_time",'
'"ssl_protocol":"$ssl_protocol",'
'"ssl_cipher":"$ssl_cipher",'
'"request_id":"$request_id"'
'}';
access_log /var/log/nginx/access.json.log json_combined;
11.3 조건부 로깅
── 헬스체크 요청 로그 제외 ──
map $request_uri $loggable {
~*^/health 0;
~*^/ready 0;
~*^/metrics 0;
default 1;
}
access_log /var/log/nginx/access.log combined if=$loggable;
── 에러 요청만 별도 로깅 ──
map $status $is_error {
~^[45] 1;
default 0;
}
access_log /var/log/nginx/error_requests.log combined if=$is_error;
── 느린 요청 로깅 (1초 이상) ──
map $request_time $is_slow {
~^[1-9] 1; # 1초 이상
~^[0-9]{2} 1; # 10초 이상
default 0;
}
access_log /var/log/nginx/slow_requests.log json_combined if=$is_slow;
11.4 도메인별 로그 분리
server {
server_name example.com;
access_log /var/log/nginx/example.com.access.log json_combined;
error_log /var/log/nginx/example.com.error.log warn;
}
server {
server_name api.example.com;
access_log /var/log/nginx/api.example.com.access.log json_combined;
error_log /var/log/nginx/api.example.com.error.log warn;
}
11.5 로그 로테이션 (logrotate)
/etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily # 매일 로테이션
missingok # 로그 파일 없어도 에러 안 남
rotate 14 # 14일치 보관
compress # gzip 압축
delaycompress # 바로 직전 파일은 압축 안 함
notifempty # 빈 파일은 로테이션 안 함
create 0640 nginx adm # 새 파일 권한
sharedscripts
postrotate
Nginx에 로그 파일 재오픈 시그널
if [ -f /run/nginx.pid ]; then
kill -USR1 $(cat /run/nginx.pid)
fi
endscript
}
12. 성능 튜닝
12.1 Worker 프로세스와 연결
── Main Context ──
worker_processes auto; # CPU 코어 수만큼 (수동: 4, 8 등)
worker_rlimit_nofile 65535; # Worker당 최대 파일 디스크립터 수
events {
worker_connections 4096; # Worker당 최대 동시 연결 수
multi_accept on; # 한 이벤트 루프에서 여러 연결 수락
use epoll; # Linux: epoll (기본값)
}
**최대 동시 연결 수 공식:**
최대 연결 = worker_processes x worker_connections
예: 4 workers x 4096 connections = 16,384 동시 연결
리버스 프록시 시 (클라이언트 + 백엔드 2개 연결):
실제 동시 클라이언트 = 16,384 / 2 = 8,192
12.2 Keepalive 설정
http {
── 클라이언트 Keepalive ──
keepalive_timeout 65; # 클라이언트 keepalive 유지 시간 (초)
keepalive_requests 1000; # 하나의 keepalive 연결로 처리할 최대 요청 수
── 타임아웃 ──
client_body_timeout 12; # 클라이언트 요청 본문 수신 타임아웃
client_header_timeout 12; # 클라이언트 요청 헤더 수신 타임아웃
send_timeout 10; # 클라이언트에 응답 전송 타임아웃
reset_timedout_connection on; # 타임아웃 연결 즉시 리셋 (메모리 해제)
}
12.3 버퍼 설정
http {
── 클라이언트 요청 버퍼 ──
client_body_buffer_size 16k; # 요청 본문 버퍼 (초과 시 디스크 기록)
client_header_buffer_size 1k; # 요청 헤더 버퍼
client_max_body_size 100m; # 최대 업로드 크기 (기본 1MB)
large_client_header_buffers 4 16k; # 큰 헤더용 버퍼
── 프록시 버퍼 ──
proxy_buffers 16 32k; # 백엔드 응답 저장 버퍼
proxy_buffer_size 16k; # 첫 번째 응답 (헤더) 버퍼
proxy_busy_buffers_size 64k; # 클라이언트로 전송 중인 버퍼 크기
proxy_temp_file_write_size 64k; # 디스크에 기록하는 임시 파일 크기
}
12.4 프로덕션 성능 튜닝 종합
/etc/nginx/nginx.conf — 프로덕션 최적화
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
events {
worker_connections 4096;
multi_accept on;
use epoll;
}
http {
── MIME & 기본 설정 ──
include /etc/nginx/mime.types;
default_type application/octet-stream;
charset utf-8;
server_tokens off; # 버전 정보 숨김
── 파일 전송 최적화 ──
sendfile on; # 커널 수준 파일 전송
tcp_nopush on; # sendfile과 함께 사용: 패킷 최적화
tcp_nodelay on; # Nagle 알고리즘 비활성화
aio on; # 비동기 I/O
── Keepalive ──
keepalive_timeout 65;
keepalive_requests 1000;
── 타임아웃 ──
client_body_timeout 12;
client_header_timeout 12;
send_timeout 10;
reset_timedout_connection on;
── 버퍼 ──
client_body_buffer_size 16k;
client_header_buffer_size 1k;
client_max_body_size 100m;
large_client_header_buffers 4 16k;
── Open File Cache ──
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
── 압축, SSL, 로깅 등 include ──
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*.conf;
}
13. URL Rewriting과 Redirection
13.1 return 디렉티브 (권장)
`return`은 `rewrite`보다 단순하고 효율적이다. URL 변경이 필요한 대부분의 경우 `return`을 먼저 고려해야 한다.
── HTTP → HTTPS 리다이렉트 ──
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
── www → non-www 정규화 ──
server {
listen 443 ssl http2;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
── 도메인 변경 리다이렉트 ──
server {
listen 80;
listen 443 ssl http2;
server_name old-domain.com www.old-domain.com;
return 301 https://new-domain.com$request_uri;
}
── 특정 경로 리다이렉트 ──
location /old-page {
return 301 /new-page;
}
── 유지보수 모드 ──
location / {
return 503; # Service Unavailable
}
error_page와 조합
error_page 503 /maintenance.html;
location = /maintenance.html {
root /var/www/html;
internal;
}
13.2 rewrite 디렉티브
`rewrite`는 정규표현식 기반의 URL 변환이 필요할 때 사용한다.
── 기본 문법 ──
rewrite regex replacement [flag];
flag: last | break | redirect (302) | permanent (301)
── 버전 없는 API 경로로 변환 ──
rewrite ^/api/v1/(.*)$ /api/$1 last;
/api/v1/users → /api/users 로 내부 리라이트
last: 새로운 location 매칭 시작
── 확장자 제거 (Clean URL) ──
rewrite ^/(.*)\.html$ /$1 permanent;
/about.html → /about (301 리다이렉트)
── 쿼리스트링 포함 리라이트 ──
rewrite ^/search/(.*)$ /search?q=$1? last;
? 를 끝에 붙이면 원래 쿼리스트링 제거
── 다국어 URL ──
rewrite ^/ko/(.*)$ /$1?lang=ko last;
rewrite ^/en/(.*)$ /$1?lang=en last;
rewrite ^/ja/(.*)$ /$1?lang=ja last;
**rewrite 플래그 비교:**
| 플래그 | 동작 | 용도 |
| ----------- | ------------------------------------- | ----------------- |
| `last` | 리라이트 후 새 location 매칭 | 내부 라우팅 변경 |
| `break` | 리라이트 후 현재 location 내에서 처리 | 현재 블록 내 변환 |
| `redirect` | 302 임시 리다이렉트 | 임시 이동 |
| `permanent` | 301 영구 리다이렉트 | 영구 이동 |
13.3 try_files 디렉티브
`try_files`는 파일/디렉토리 존재 여부를 순차적으로 확인하며, SPA나 프레임워크에서 필수적이다.
── SPA (React, Vue, Angular 등) ──
location / {
root /var/www/spa;
try_files $uri $uri/ /index.html;
1. $uri: 요청 파일이 있는지 확인
2. $uri/: 디렉토리가 있는지 확인
3. /index.html: 위 모두 없으면 index.html (SPA 라우팅)
}
── Next.js / Nuxt.js ──
location / {
try_files $uri $uri/ @proxy;
}
location @proxy {
proxy_pass http://127.0.0.1:3000;
include /etc/nginx/conf.d/proxy-params.conf;
}
── PHP (WordPress, Laravel) ──
location / {
try_files $uri $uri/ /index.php?$args;
}
── 정적 파일 우선 → 백엔드 폴백 ──
location / {
root /var/www/static;
try_files $uri @backend;
}
location @backend {
proxy_pass http://app_server;
}
13.4 조건부 리다이렉트
── 모바일 디바이스 리다이렉트 ──
if ($http_user_agent ~* "(Android|iPhone|iPad)") {
return 302 https://m.example.com$request_uri;
}
── 특정 쿼리 파라미터 기반 ──
if ($arg_redirect) {
return 302 $arg_redirect;
}
── map으로 복잡한 리다이렉트 관리 ──
map $request_uri $redirect_uri {
/old-blog/post-1 /blog/new-post-1;
/old-blog/post-2 /blog/new-post-2;
/products/legacy /shop/all;
default "";
}
server {
if ($redirect_uri) {
return 301 $redirect_uri;
}
}
14. 정적 파일 서빙 최적화
14.1 핵심 파일 전송 디렉티브
http {
── sendfile ──
커널의 sendfile() 시스템 콜을 사용하여 파일을 직접 소켓으로 전송
유저 스페이스 메모리 복사를 제거하여 CPU 사용량과 컨텍스트 스위칭 감소
sendfile on;
── tcp_nopush ──
sendfile과 함께 작동: HTTP 헤더와 파일 시작 부분을 하나의 패킷으로 전송
네트워크 패킷 수를 줄여 대역폭 효율 향상
tcp_nopush on;
── tcp_nodelay ──
Nagle 알고리즘 비활성화: 작은 패킷을 즉시 전송
keepalive 연결에서 지연 시간 감소 (tcp_nopush와 함께 사용 가능)
tcp_nodelay on;
}
**전송 방식 비교:**
sendfile off (기본):
디스크 → 커널 버퍼 → 유저 메모리 (Nginx) → 커널 소켓 버퍼 → 네트워크
[읽기] [복사] [쓰기]
sendfile on:
디스크 → 커널 버퍼 ─────────────────────────→ 커널 소켓 버퍼 → 네트워크
[읽기] [제로 카피: CPU 개입 없음] [전송]
14.2 Open File Cache
반복적으로 요청되는 파일의 디스크립터, 크기, 수정 시간을 캐시하여 파일시스템 조회를 최소화한다.
http {
최대 10,000개 파일 정보 캐시, 30초간 미사용 시 제거
open_file_cache max=10000 inactive=30s;
캐시된 정보를 60초마다 재검증
open_file_cache_valid 60s;
최소 2회 이상 요청된 파일만 캐시 (1회성 요청 필터)
open_file_cache_min_uses 2;
파일 없음(ENOENT) 에러도 캐시 (존재하지 않는 파일 반복 조회 방지)
open_file_cache_errors on;
}
14.3 정적 파일 서빙 종합 설정
server {
listen 443 ssl http2;
server_name static.example.com;
root /var/www/static;
── 이미지 ──
location ~* \.(jpg|jpeg|png|gif|ico|webp|avif|svg)$ {
expires 30d;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding";
access_log off;
log_not_found off; # 404 로그도 비활성화
이미지 전용 제한
limit_rate 2m; # 연결당 2MB/s
}
── CSS/JS (캐시 버스팅 전략과 함께) ──
location ~* \.(css|js)$ {
expires 365d; # 해시 기반 파일명이면 장기 캐시
add_header Cache-Control "public, immutable";
gzip_static on;
brotli_static on;
}
── 폰트 ──
location ~* \.(woff|woff2|ttf|eot|otf)$ {
expires 365d;
add_header Cache-Control "public, immutable";
add_header Access-Control-Allow-Origin "*";
}
── 미디어 파일 ──
location ~* \.(mp4|webm|ogg|mp3|wav)$ {
expires 30d;
add_header Cache-Control "public";
Range 요청 지원 (동영상 시크)
add_header Accept-Ranges bytes;
}
── 디렉토리 리스팅 방지 ──
location / {
autoindex off;
}
── 숨김 파일 접근 차단 ──
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
14.4 HTTP/2 Server Push (선택)
주요 리소스를 클라이언트 요청 전에 미리 전송
location = /index.html {
http2_push /css/main.css;
http2_push /js/app.js;
http2_push /images/logo.webp;
}
> **참고:** HTTP/2 Server Push는 일부 브라우저에서 지원이 중단되고 있어, `103 Early Hints`가 대안으로 떠오르고 있다.
15. 헬스체크와 모니터링
15.1 Stub Status (기본 모니터링)
Nginx OSS에 포함된 `stub_status` 모듈로 실시간 연결 상태를 확인할 수 있다.
server {
listen 8080; # 별도 포트로 분리
server_name localhost;
내부 네트워크만 접근 허용
allow 10.0.0.0/8;
allow 127.0.0.1;
deny all;
location /nginx_status {
stub_status;
}
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
}
**stub_status 출력 예시:**
Active connections: 291
server accepts handled requests
16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106
| 항목 | 의미 |
| -------------------- | ----------------------------------------------- |
| `Active connections` | 현재 활성 연결 수 (Reading + Writing + Waiting) |
| `accepts` | 총 수락된 연결 수 |
| `handled` | 총 처리된 연결 수 (= accepts면 정상) |
| `requests` | 총 처리된 요청 수 |
| `Reading` | 클라이언트 요청 헤더를 읽는 중인 연결 수 |
| `Writing` | 클라이언트에 응답을 보내는 중인 연결 수 |
| `Waiting` | keepalive 유휴 연결 수 |
15.2 패시브 헬스체크 (Upstream 모니터링)
Nginx 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;
server 10.0.0.3:8080 max_fails=3 fail_timeout=30s backup;
max_fails=3: 30초 내 3회 연속 실패 시 서버를 비정상으로 판정
fail_timeout=30s: 비정상 판정 후 30초간 해당 서버로 요청 전송 안 함
30초 후 다시 요청을 보내 복구 여부 확인
}
server {
location / {
proxy_pass http://backend;
어떤 응답을 "실패"로 판정할지 설정
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
proxy_next_upstream_timeout 10s; # 다음 서버 시도 최대 시간
proxy_next_upstream_tries 3; # 최대 재시도 횟수
}
}
15.3 액티브 헬스체크 (NGINX Plus 또는 외부 솔루션)
── NGINX Plus에서의 액티브 헬스체크 ──
upstream backend {
zone backend_zone 64k; # 공유 메모리 존 필수
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
server {
location / {
proxy_pass http://backend;
health_check interval=5s # 5초마다 체크
fails=3 # 3회 실패 시 비정상
passes=2 # 2회 성공 시 복구
uri=/health; # 헬스체크 엔드포인트
}
}
15.4 Prometheus 연동 (nginx-prometheus-exporter)
docker-compose.yml
services:
nginx-exporter:
image: nginx/nginx-prometheus-exporter:1.3
command:
- --nginx.scrape-uri=http://nginx:8080/nginx_status
ports:
- '9113:9113'
depends_on:
- nginx
prometheus.yml
scrape_configs:
- job_name: 'nginx'
static_configs:
- targets: ['nginx-exporter:9113']
15.5 커스텀 헬스체크 엔드포인트
── Liveness Probe (Nginx 자체 동작 확인) ──
location = /healthz {
access_log off;
return 200 "alive\n";
add_header Content-Type text/plain;
}
── Readiness Probe (백엔드 연결 포함 확인) ──
location = /readyz {
access_log off;
proxy_pass http://backend/health;
proxy_connect_timeout 2s;
proxy_read_timeout 2s;
백엔드 응답 실패 시 503
error_page 502 503 504 = @not_ready;
}
location @not_ready {
return 503 "not ready\n";
add_header Content-Type text/plain;
}
Kubernetes에서의 활용
apiVersion: v1
kind: Pod
spec:
containers:
- name: nginx
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
종합 프로덕션 설정 체크리스트
프로덕션 환경에 Nginx를 배포할 때 확인해야 할 핵심 항목을 정리한다.
| 카테고리 | 항목 | 상태 |
| -------------- | ---------------------------------------- | ---- |
| **아키텍처** | worker_processes auto 설정 | [ ] |
| **아키텍처** | worker_connections 적정 값 설정 | [ ] |
| **SSL/TLS** | TLSv1.2 + TLSv1.3만 활성화 | [ ] |
| **SSL/TLS** | 강력한 Cipher Suites 설정 | [ ] |
| **SSL/TLS** | OCSP Stapling 활성화 | [ ] |
| **SSL/TLS** | HSTS 헤더 설정 | [ ] |
| **SSL/TLS** | HTTP → HTTPS 리다이렉트 | [ ] |
| **보안** | server_tokens off | [ ] |
| **보안** | 보안 헤더 (CSP, X-Frame-Options 등) 설정 | [ ] |
| **보안** | Rate Limiting 설정 | [ ] |
| **보안** | 관리자 페이지 접근 제어 | [ ] |
| **성능** | sendfile, tcp_nopush, tcp_nodelay 활성화 | [ ] |
| **성능** | Gzip/Brotli 압축 설정 | [ ] |
| **성능** | open_file_cache 설정 | [ ] |
| **성능** | 정적 파일 브라우저 캐싱 설정 | [ ] |
| **성능** | Upstream keepalive 설정 | [ ] |
| **캐싱** | proxy_cache 또는 fastcgi_cache 설정 | [ ] |
| **캐싱** | proxy_cache_use_stale 설정 | [ ] |
| **모니터링** | stub_status 활성화 (내부 전용) | [ ] |
| **모니터링** | 헬스체크 엔드포인트 구성 | [ ] |
| **로깅** | JSON 로그 포맷 설정 | [ ] |
| **로깅** | 로그 로테이션 설정 | [ ] |
| **로깅** | 헬스체크/정적파일 로그 제외 | [ ] |
| **프록시** | 필수 프록시 헤더 설정 | [ ] |
| **프록시** | WebSocket 프록시 설정 (필요 시) | [ ] |
| **로드밸런싱** | 적절한 알고리즘 선택 | [ ] |
| **로드밸런싱** | 백업 서버 구성 | [ ] |
참고 자료
- [Nginx Official Documentation](https://nginx.org/en/docs/)
- [Nginx Beginner's Guide](https://nginx.org/en/docs/beginners_guide.html)
- [DigitalOcean - Understanding Nginx Configuration File Structure](https://www.digitalocean.com/community/tutorials/understanding-the-nginx-configuration-file-structure-and-configuration-contexts)
- [Inside NGINX: How We Designed for Performance and Scale](https://blog.nginx.org/blog/inside-nginx-how-we-designed-for-performance-scale)
- [NGINX Reverse Proxy Guide](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy)
- [Nginx WebSocket Proxying](https://nginx.org/en/docs/http/websocket.html)
- [NGINX HTTP Load Balancing](https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/)
- [NGINX TLS 1.3 Hardening Guide](https://www.getpagespeed.com/server-setup/nginx/nginx-tls-1-3-hardening/amp)
- [A Guide to Caching with NGINX](https://blog.nginx.org/blog/nginx-caching-guide)
- [Rate Limiting with NGINX](https://blog.nginx.org/blog/rate-limiting-nginx)
- [NGINX Gzip Compression Guide](https://www.getpagespeed.com/server-setup/nginx/nginx-gzip-compression)
- [Tuning NGINX for Performance](https://www.f5.com/company/blog/nginx/tuning-nginx)
- [Creating NGINX Rewrite Rules](https://blog.nginx.org/blog/creating-nginx-rewrite-rules)
- [NGINX sendfile, tcp_nopush, tcp_nodelay Explained](https://www.getpagespeed.com/server-setup/nginx/nginx-sendfile-tcp-nopush-tcp-nodelay)
- [NGINX Health Checks](https://docs.nginx.com/nginx/admin-guide/load-balancer/http-health-check/)
- [Mozilla SSL Configuration Generator](https://ssl-config.mozilla.org/)
현재 단락 (1/1023)
Nginx는 Apache httpd의 프로세스/스레드 기반 모델과 근본적으로 다른 **이벤트 기반(Event-Driven) 아키텍처**를 채택했다. 이 설계 철학이 Nginx가 단...