Nginx는 전 세계에서 가장 인기 있는 웹 서버로, 전체 웹사이트의 30% 이상에서 사용됩니다. 정적 파일 제공, 리버스 프록시 설정, SSL 구성, 로드 밸런싱 등 이 포괄적인 Nginx 설정 가이드는 바로 복사하여 사용할 수 있는 프로덕션 레벨의 예제를 제공합니다.
Nginx 기본 사항
Nginx("엔진 엑스"로 발음)는 고성능 HTTP 서버, 리버스 프록시, 로드 밸런서입니다. 이벤트 기반 아키텍처로 적은 메모리로 수천 개의 동시 연결을 처리합니다. 주 설정 파일은 보통 /etc/nginx/nginx.conf에 있습니다.
# Main configuration file structure
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto; # One worker per CPU core
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 1024; # Max connections per worker
multi_accept on; # Accept multiple connections at once
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging format
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log main;
sendfile on; # Efficient file transfer
tcp_nopush on; # Optimize TCP packets
tcp_nodelay on; # Disable Nagle's algorithm
keepalive_timeout 65; # Keep connections alive
types_hash_max_size 2048;
# Include site configurations
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}정적 사이트 설정
가장 간단하고 일반적인 사용 사례: 디스크에서 HTML, CSS, JavaScript, 이미지 파일을 직접 제공합니다.
# Static website configuration
# /etc/nginx/conf.d/static-site.conf
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# Document root
root /var/www/example.com/html;
index index.html index.htm;
# Main location block
location / {
try_files $uri $uri/ =404;
}
# Cache static assets aggressively
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# Cache HTML files with shorter duration
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
# Deny access to hidden files (.htaccess, .git, etc.)
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Custom error pages
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
}리버스 프록시 설정
리버스 프록시는 클라이언트와 백엔드 애플리케이션(Node.js, Python, Go 등) 사이에 위치하여 요청을 전달하고 응답을 반환합니다.
# Reverse proxy to Node.js/Python/Go application
# /etc/nginx/conf.d/app-proxy.conf
server {
listen 80;
server_name app.example.com;
# Max upload size
client_max_body_size 50M;
# Proxy all requests to the backend application
location / {
proxy_pass http://127.0.0.1:3000;
# Pass the real client IP to the backend
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;
# WebSocket support (for Socket.IO, etc.)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffering settings
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
# Serve static files directly (bypass the backend)
location /static/ {
alias /var/www/app/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Health check endpoint
location /health {
proxy_pass http://127.0.0.1:3000/health;
access_log off;
}
access_log /var/log/nginx/app.access.log;
error_log /var/log/nginx/app.error.log;
}SSL/TLS 설정
HTTPS로 사이트를 보호하는 것은 현대 웹 개발에서 필수입니다. 이 설정은 Let's Encrypt 인증서를 사용하고 강력한 암호 제품군과 HSTS를 포함합니다.
# SSL/TLS configuration with Let's Encrypt
# /etc/nginx/conf.d/ssl-site.conf
# Redirect all HTTP traffic to HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# Let's Encrypt ACME challenge
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# 301 permanent redirect to HTTPS
location / {
return 301 https://$server_name$request_uri;
}
}
# HTTPS server block
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
# Let's Encrypt certificate paths
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# SSL session settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
# Modern TLS configuration (TLS 1.2 + 1.3 only)
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:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# OCSP Stapling (faster certificate verification)
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# HSTS (force HTTPS for 2 years, including subdomains)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
root /var/www/example.com/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
access_log /var/log/nginx/example.com.ssl.access.log;
error_log /var/log/nginx/example.com.ssl.error.log;
}SPA (React/Vue/Angular) 설정
싱글 페이지 애플리케이션은 클라이언트 사이드 라우팅을 사용하므로 모든 경로가 index.html로 폴백되어야 합니다.
# SPA configuration (React, Vue, Angular, Next.js static export)
# /etc/nginx/conf.d/spa.conf
server {
listen 80;
server_name spa.example.com;
root /var/www/spa/dist;
index index.html;
# The key directive for SPAs: fallback to index.html
# This ensures client-side routing works correctly
location / {
try_files $uri $uri/ /index.html;
}
# Cache JavaScript and CSS bundles (with hash in filename)
location ~* \.(js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Cache images, fonts, and media
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|avif|woff|woff2|ttf|eot|mp4|webm)$ {
expires 30d;
add_header Cache-Control "public";
access_log off;
}
# Do NOT cache index.html (always serve the latest version)
location = /index.html {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
# API proxy (forward /api requests to the backend)
location /api/ {
proxy_pass http://127.0.0.1:4000;
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;
}
# Deny access to source maps in production
location ~* \.map$ {
deny all;
}
}로드 밸런싱
고가용성과 향상된 성능을 위해 들어오는 트래픽을 여러 백엔드 서버에 분산합니다.
# Load balancing across multiple backend servers
# /etc/nginx/conf.d/load-balancer.conf
# Define backend server group
upstream app_servers {
# Round-robin (default) - requests distributed evenly
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
# Mark a server as backup (used only when others are down)
server 10.0.0.4:3000 backup;
# Health check: mark server down after 3 failed attempts
# max_fails=3 fail_timeout=30s (default)
}
# Weighted load balancing (send more traffic to powerful servers)
upstream app_weighted {
server 10.0.0.1:3000 weight=5; # Gets 5x the traffic
server 10.0.0.2:3000 weight=3; # Gets 3x the traffic
server 10.0.0.3:3000 weight=1; # Gets 1x the traffic
}
# Least connections (send to the server with fewest active requests)
upstream app_least_conn {
least_conn;
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
}
# IP hash (same client always goes to same server - sticky sessions)
upstream app_ip_hash {
ip_hash;
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
}
server {
listen 80;
server_name lb.example.com;
location / {
proxy_pass http://app_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Connection keep-alive to backends
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# Simple health check endpoint
location /nginx-health {
access_log off;
return 200 "OK";
add_header Content-Type text/plain;
}
}보안 헤더
HTTP 보안 헤더는 클릭재킹, XSS, 콘텐츠 인젝션과 같은 일반적인 공격으로부터 사이트를 보호합니다.
# Security headers configuration
# Add these inside your server {} block or create a snippet
# /etc/nginx/snippets/security-headers.conf
# Include with: include /etc/nginx/snippets/security-headers.conf;
# Prevent clickjacking: deny embedding in iframes
add_header X-Frame-Options "SAMEORIGIN" always;
# Prevent MIME-type sniffing
add_header X-Content-Type-Options "nosniff" always;
# Enable XSS protection (legacy browsers)
add_header X-XSS-Protection "1; mode=block" always;
# Control referrer information sent with requests
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Content Security Policy (customize based on your needs)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 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 'self';" always;
# Permissions Policy (formerly Feature-Policy)
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;
# Prevent information leakage
add_header X-Permitted-Cross-Domain-Policies "none" always;
# HSTS (only add if you have SSL configured)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Hide Nginx version number
server_tokens off;Gzip 압축
Gzip 압축을 활성화하면 전송 응답 크기를 최대 90%까지 줄여 페이지 로드 시간을 크게 개선할 수 있습니다.
# Gzip compression configuration
# Add inside the http {} block in nginx.conf
# Enable gzip compression
gzip on;
# Minimum file size to compress (skip tiny files)
gzip_min_length 256;
# Compression level (1-9, higher = more CPU, smaller files)
# Level 6 is a good balance between compression ratio and CPU usage
gzip_comp_level 6;
# Number and size of compression buffers
gzip_buffers 16 8k;
# Compress responses for HTTP/1.0 clients too
gzip_http_version 1.0;
# Compress all text-based content types
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/x-javascript
application/xml
application/xml+rss
application/atom+xml
application/vnd.ms-fontobject
font/opentype
font/ttf
image/svg+xml
image/x-icon;
# Add Vary: Accept-Encoding header (important for caching proxies)
gzip_vary on;
# Disable gzip for old IE browsers
gzip_disable "MSIE [1-6]\.";
# Enable gzip for proxied requests too
gzip_proxied any;속도 제한
속도 제한은 남용, 브루트포스 공격, DDoS로부터 서버를 보호합니다. Nginx의 limit_req 모듈은 리키 버킷 알고리즘을 사용합니다.
# Rate limiting configuration
# Define zones in the http {} block, apply in server/location blocks
# ── Define rate limit zones (in http {} block) ──
# General rate limit: 10 requests/second per IP
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
# Strict rate limit for login/auth: 5 requests/minute per IP
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
# API rate limit: 30 requests/second per IP
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
# Rate limit by server name (protect against virtual host abuse)
limit_req_zone $server_name zone=per_server:10m rate=100r/s;
# ── Apply rate limits (in server {} block) ──
server {
listen 80;
server_name api.example.com;
# Custom error page for rate-limited requests
error_page 429 = @rate_limited;
location @rate_limited {
default_type application/json;
return 429 '{"error": "Too many requests. Please try again later."}';
}
# General pages: allow burst of 20, no delay for first 10
location / {
limit_req zone=general burst=20 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:3000;
}
# Login endpoint: strict rate limiting
location /api/auth/login {
limit_req zone=login burst=3 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:3000;
}
# API endpoints: higher limit with burst
location /api/ {
limit_req zone=api burst=50 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:3000;
}
# Whitelist certain IPs from rate limiting
# geo $limit {
# default 1;
# 10.0.0.0/8 0; # Internal network
# 192.168.0.0/16 0; # Local network
# }
# map $limit $limit_key {
# 0 "";
# 1 $binary_remote_addr;
# }
# limit_req_zone $limit_key zone=custom:10m rate=10r/s;
}주요 디렉티브 참조
가장 자주 사용되는 Nginx 디렉티브와 설명의 빠른 참조입니다.
| 디렉티브 | 설명 |
|---|---|
| worker_processes | 워커 프로세스 수 (auto로 CPU 수에 맞춤) |
| worker_connections | 워커당 최대 동시 연결 수 |
| server_name | 이 서버 블록이 응답하는 도메인 이름 |
| listen | 리슨할 포트와 프로토콜 |
| root | 파일 제공의 루트 디렉토리 |
| index | 디렉토리 요청의 기본 파일 |
| location | 요청 URI에 매칭하여 특정 설정 적용 |
| proxy_pass | 백엔드 서버로 요청 전달 |
| try_files | 순서대로 파일을 찾고, 마지막 옵션으로 폴백 |
| ssl_certificate | SSL 인증서 파일 경로 |
| ssl_certificate_key | SSL 개인 키 파일 경로 |
| add_header | 커스텀 HTTP 응답 헤더 추가 |
| gzip | gzip 압축 활성화/비활성화 |
| expires | 정적 자산의 Cache-Control max-age 설정 |
| upstream | 로드 밸런싱을 위한 백엔드 서버 그룹 정의 |
| limit_req_zone | 속도 제한용 공유 메모리 영역 정의 |
| error_page | 특정 상태 코드의 커스텀 에러 페이지 정의 |
| access_log | 액세스 로그의 경로와 형식 |
| error_log | 에러 로그의 경로와 레벨 |
| client_max_body_size | 클라이언트 요청 본문의 최대 허용 크기 |
| sendfile | 커널 sendfile을 사용한 효율적인 파일 전송 활성화 |
자주 묻는 질문
Nginx와 Apache의 차이점은?
Nginx는 이벤트 기반 비동기 아키텍처로 적은 메모리로 많은 동시 연결을 효율적으로 처리합니다. Apache는 연결당 프로세스/스레드 모델로 높은 부하에서 더 많은 메모리를 사용합니다. 많은 프로덕션 환경에서 Nginx를 Apache 앞에 리버스 프록시로 배치합니다.
리로드 전에 Nginx 설정을 테스트하려면?
리로드 전에 항상 "nginx -t"를 실행하세요. 이 명령은 설정 파일의 구문 오류를 검사합니다. 테스트를 통과하면 "nginx -s reload"로 안전하게 리로드할 수 있습니다.
Let's Encrypt로 SSL 인증서를 설정하려면?
Certbot을 설치하고 "certbot --nginx -d yourdomain.com"을 실행합니다. Certbot이 자동으로 인증서를 획득하고 Nginx 설정을 수정합니다. 자동 갱신도 설정됩니다.
"proxy_set_header X-Real-IP $remote_addr"의 역할은?
Nginx가 리버스 프록시로 작동할 때 백엔드 앱은 Nginx의 IP를 클라이언트 IP로 인식합니다. 이 헤더로 원래 클라이언트 IP를 백엔드에 전달합니다.
Nginx에서 HTTP를 HTTPS로 리다이렉트하려면?
포트 80에서 리슨하는 별도의 서버 블록을 만들고 "return 301 https://$server_name$request_uri;"를 설정합니다. 모든 HTTP 트래픽이 HTTPS로 영구 리다이렉트됩니다.
try_files 디렉티브란? SPA에 왜 중요한가?
try_files는 Nginx에게 파일 존재를 순서대로 확인하게 합니다. SPA의 경우 "try_files $uri $uri/ /index.html"로 파일이 없으면 index.html로 폴백합니다. SPA의 경로(/about 등)는 디스크의 실제 파일에 대응하지 않기 때문에 중요합니다.
이 Nginx 설정들은 가장 일반적인 프로덕션 시나리오를 다룹니다. 리로드 전에 항상 "nginx -t"로 테스트하고, 액세스 로그와 에러 로그를 정기적으로 모니터링하세요.