Nginx는 고성능 웹 서버이자 리버스 프록시 서버로, 가볍고 빠른 처리 성능으로 널리 사용된다.
이 문서는 실무에서 자주 접하는 핵심 설정들을 정리한다.
Nginx 설정 파일(nginx.conf)은 블록(block) 단위로 구성된다.
main (전역)
├── events { } # 연결 처리 방식
└── http { } # HTTP 관련 설정
├── upstream { } # 로드 밸런싱 대상 서버 그룹
└── server { } # 가상 호스트 설정
└── location { } # 요청 경로별 처리
# nginx.conf 기본 구조
worker_processes auto;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name example.com;
location / {
root /var/www/html;
index index.html;
}
}
}
Nginx는 멀티 프로세스 모델로 동작한다.
master process 1개가 worker process 여러 개를 관리한다.
# CPU 코어 수에 맞게 자동 설정 (권장)
worker_processes auto;
events {
# worker 하나가 동시에 처리할 수 있는 최대 연결 수
worker_connections 1024;
# 여러 연결을 한 번에 수락 (성능 향상)
multi_accept on;
# Linux 에서 가장 효율적인 이벤트 처리 방식
use epoll;
}
💡 최대 동시 처리 가능 연결 수 =
worker_processes×worker_connections
http {
# MIME 타입 정의 파일 포함
include mime.types;
default_type application/octet-stream;
# sendfile: OS 커널이 직접 파일 전송 (성능 향상)
sendfile on;
# 패킷을 모아서 한 번에 전송 (sendfile on 일때 효과적)
tcp_nopush on;
# 작은 패킷도 즉시 전송 (지연 감소)
tcp_nodelay on;
# Keep-Alive 연결 유지 시간 (초)
keepalive_timeout 65;
}
하나의 Nginx에서 여러 도메인(가상 호스트) 을 운영할 수 있다.
# HTTP 서버
server {
listen 80;
server_name example.com www.example.com;
# ...
}
# 다른 도메인
server {
listen 80;
server_name another.com;
# ...
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
| 우선순위 | 기호 | 방식 | 설명 |
|---|---|---|---|
| 1 | = | Exact Match | 경로 정확히 일치 |
| 2 | ^~ | Prefix Match (우선) | prefix 일치 시 정규식 검사 중단 |
| 3 | ~ / ~* | Regex Match | 정규식 일치 (~* 는 대소문자 무시) |
| 4 | (없음) | Prefix Match | 일반 전방 일치 |
# ① 정확 일치 (최우선)
location = /health {
return 200 'OK';
}
# ② prefix 우선 일치 (정규식보다 우선)
location ^~ /static/ {
root /var/www;
}
# ③ 정규식 일치 (대소문자 구분)
location ~ ^/api/v\d+/ {
proxy_pass http://localhost:8080;
}
# ③ 정규식 일치 (대소문자 무시)
location ~* \.(jpg|jpeg|png|gif)$ {
expires 30d;
}
# ④ 일반 prefix 일치
location /app/ {
proxy_pass http://localhost:3000/;
}
/path vs /path/ 주의사항location /detector/ 는 /detector (trailing slash 없음) 요청을 매칭하지 않는다.
두 경우를 모두 처리하려면 아래처럼 설정한다.
location = /detector {
return 301 /detector/;
}
location /detector/ {
proxy_pass http://localhost:8080/;
}
/) 의 의미proxy_pass 끝에 / 가 붙는지 여부에 따라, location prefix를 백엔드로 전달할지 말지가 결정된다.
| 설정 | 클라이언트 요청 | 백엔드로 전달되는 경로 |
|---|---|---|
proxy_pass http://localhost:8080; | /detector/api | /detector/api (prefix 유지) |
proxy_pass http://localhost:8080/; | /detector/api | /api (prefix 제거) |
# prefix 유지: /detector/api → localhost:8080/detector/api
location /detector/ {
proxy_pass http://localhost:8080;
}
# prefix 제거: /detector/api → localhost:8080/api
location /detector/ {
proxy_pass http://localhost:8080/;
}
정규식 location에서는 proxy_pass에 URI(trailing slash 포함)를 명시하면 nginx가 설정 로드 시 에러를 발생시킨다.
# ❌ 설정 에러 발생
location ~ ^/detector/\d+ {
proxy_pass http://localhost:8080/;
}
# ✅ URI 없이만 사용 가능
location ~ ^/detector/\d+ {
proxy_pass http://localhost:8080;
}
prefix를 제거하고 싶다면 rewrite 를 사용한다.
location ~ ^/detector/(.*)$ {
rewrite ^/detector/(.*)$ /$1 break;
proxy_pass http://localhost:8080;
# /detector/api/test → localhost:8080/api/test
}
리버스 프록시 시 클라이언트 정보를 백엔드로 전달하기 위해 헤더를 설정한다.
location /api/ {
proxy_pass http://localhost:8080/;
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;
}
| 헤더 | 설명 |
|---|---|
Host | 원본 요청의 호스트명 |
X-Real-IP | 클라이언트 실제 IP |
X-Forwarded-For | 프록시를 거친 IP 체인 |
X-Forwarded-Proto | 원본 요청 프로토콜 (http/https) |
server {
listen 443 ssl;
server_name example.com;
# 인증서 경로
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# 권장 프로토콜 (구버전 TLS 제외)
ssl_protocols TLSv1.2 TLSv1.3;
# 권장 암호화 알고리즘
ssl_ciphers HIGH:!aNULL:!MD5;
# 서버 암호화 알고리즘 우선 사용
ssl_prefer_server_ciphers on;
# SSL 세션 캐시 (성능 향상)
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
location / {
proxy_pass http://localhost:3000;
}
}
💡 Let's Encrypt + Certbot 을 사용하면 무료 SSL 인증서를 자동으로 발급/갱신할 수 있다.
여러 백엔드 서버에 트래픽을 분산한다.
upstream backend {
# 기본: Round Robin (순서대로 분산)
server 127.0.0.1:8001;
server 127.0.0.1:8002;
server 127.0.0.1:8003;
}
server {
location /api/ {
proxy_pass http://backend/;
}
}
upstream backend {
# least_conn: 연결 수가 가장 적은 서버로 분산
least_conn;
server 127.0.0.1:8001;
server 127.0.0.1:8002;
}
upstream backend {
# ip_hash: 같은 클라이언트 IP는 항상 같은 서버로 (세션 유지)
ip_hash;
server 127.0.0.1:8001;
server 127.0.0.1:8002;
}
upstream backend {
# weight: 가중치 설정 (8001이 트래픽 3배 더 받음)
server 127.0.0.1:8001 weight=3;
server 127.0.0.1:8002 weight=1;
}
upstream backend {
server 127.0.0.1:8001;
server 127.0.0.1:8002 backup; # 나머지 서버 장애시에만 사용
server 127.0.0.1:8003 down; # 사용 안 함 (점검용)
}
server {
listen 80;
server_name example.com;
# 루트 디렉토리 지정
root /var/www/html;
# 기본 인덱스 파일
index index.html index.htm;
location / {
# 파일 → 디렉토리 → 404 순으로 시도
try_files $uri $uri/ =404;
}
# SPA(React, Vue 등) 라우팅 처리
location / {
try_files $uri $uri/ /index.html;
}
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
백엔드 응답을 Nginx가 캐싱하여 백엔드 부하를 줄인다.
http {
# 캐시 저장 경로 및 옵션 정의
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=my_cache:10m
max_size=1g
inactive=60m;
server {
location /api/ {
proxy_pass http://localhost:8080/;
proxy_cache my_cache;
proxy_cache_valid 200 10m; # 200 응답은 10분 캐시
proxy_cache_valid 404 1m; # 404 응답은 1분 캐시
# 캐시 상태를 응답 헤더에 표시 (HIT/MISS/BYPASS)
add_header X-Cache-Status $upstream_cache_status;
}
}
}
http {
# 로그 포맷 정의
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;
# 에러 로그 (레벨: debug, info, notice, warn, error, crit)
error_log /var/log/nginx/error.log warn;
server {
# 특정 서버만 로그 비활성화
access_log off;
}
}
http {
# Nginx 버전 정보 숨기기
server_tokens off;
server {
# 클릭재킹 방지
add_header X-Frame-Options "SAMEORIGIN";
# XSS 필터 활성화
add_header X-XSS-Protection "1; mode=block";
# MIME 타입 스니핑 방지
add_header X-Content-Type-Options "nosniff";
# HTTPS 강제 (HSTS)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 특정 HTTP 메서드만 허용
if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE)$) {
return 405;
}
}
}
http {
# 클라이언트 요청 body 최대 크기 (파일 업로드 등)
client_max_body_size 10m;
}
location /admin/ {
allow 192.168.1.0/24; # 특정 대역만 허용
allow 127.0.0.1;
deny all; # 나머지 차단
}
응답 데이터를 압축하여 전송 속도를 높인다.
http {
gzip on;
# 압축 레벨 (1~9, 높을수록 압축률 높고 CPU 사용량 증가)
gzip_comp_level 6;
# 최소 압축 대상 크기 (너무 작은 파일은 압축 불필요)
gzip_min_length 1000;
# 압축 대상 MIME 타입
gzip_types
text/plain
text/css
text/javascript
application/json
application/javascript
application/xml
image/svg+xml;
# 프록시 캐시와 함께 사용 시 필요
gzip_vary on;
}
http {
# 클라이언트로부터 요청 헤더를 읽는 타임아웃
client_header_timeout 10s;
# 클라이언트로부터 요청 body를 읽는 타임아웃
client_body_timeout 10s;
# 클라이언트에게 응답을 전송하는 타임아웃
send_timeout 10s;
# 프록시 서버 연결 타임아웃
proxy_connect_timeout 5s;
# 프록시 서버로부터 응답을 받는 타임아웃
proxy_read_timeout 60s;
# 프록시 서버로 요청을 전송하는 타임아웃
proxy_send_timeout 60s;
}
| 설정 항목 | 핵심 포인트 |
|---|---|
| worker | worker_processes auto + worker_connections 로 동시 처리량 결정 |
| location | 우선순위: = > ^~ > ~ > prefix |
| proxy_pass | trailing / 유무로 prefix 제거 여부 결정 |
| 정규식 location | proxy_pass에 URI 불가 → rewrite 활용 |
| SSL | TLSv1.2 이상 사용, Let's Encrypt 권장 |
| 로드 밸런싱 | Round Robin / least_conn / ip_hash / weight |
| 보안 | server_tokens off, 보안 헤더, IP 제한 |
| 성능 | gzip, sendfile, proxy_cache, keepalive 활용 |