Skip to content

필사 모드: Nginx 설정 완벽 가이드: 아키텍처부터 프로덕션 최적화까지 15가지 핵심 주제

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

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가 단...

작성 글자: 0원문 글자: 34,422작성 단락: 0/1023