DevToolBox무료
블로그

Nginx 설정 예시: 리버스 프록시, SSL, 정적 사이트

11분 읽기by DevToolBox

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_certificateSSL 인증서 파일 경로
ssl_certificate_keySSL 개인 키 파일 경로
add_header커스텀 HTTP 응답 헤더 추가
gzipgzip 압축 활성화/비활성화
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"로 테스트하고, 액세스 로그와 에러 로그를 정기적으로 모니터링하세요.

𝕏 Twitterin LinkedIn
도움이 되었나요?

최신 소식 받기

주간 개발 팁과 새 도구 알림을 받으세요.

스팸 없음. 언제든 구독 해지 가능.

Try These Related Tools

NXNginx Config Generator.ht.htaccess Generator🛡️CSP Header Generator

Related Articles

Docker Compose 치트시트: 서비스, 볼륨, 네트워크

Docker Compose 레퍼런스: 서비스 정의, 볼륨, 네트워크, 환경 변수, 스택 예시.

.htaccess 리다이렉트 치트시트: 복사-붙여넣기 예제

완전한 .htaccess 리다이렉트 참조. 301 리다이렉트, HTTPS, 에러 페이지, 보안 헤더.

Nginx Config Generator - nginx.conf 온라인 생성기 (무료 도구 + 완벽 가이드)

프로덕션용 nginx.conf를 온라인으로 생성하세요. 서버 블록, 리버스 프록시, SSL/TLS, 로드 밸런싱, gzip, 보안 헤더, 속도 제한, 캐싱 및 일반적인 패턴을 다룹니다.