Nginx 완벽 가이드

Nginx란?

Nginx(엔진엑스)는 2004년 러시아 개발자 Igor Sysoev가 만든 고성능 웹 서버이자 리버스 프록시 서버입니다. 처음에는 Rambler.ru라는 러시아 포털 사이트의 C10K 문제(동시에 10,000개의 연결을 처리하는 문제)를 해결하기 위해 개발되었으며, 현재는 전 세계 웹사이트의 30% 이상에서 사용되고 있습니다.

Apache가 오랫동안 웹 서버 시장을 지배했지만, Nginx는 높은 동시 접속 처리 능력과 낮은 메모리 사용량으로 빠르게 시장 점유율을 확대했습니다. Netflix, Airbnb, GitHub, WordPress.com 등 트래픽이 많은 대형 사이트들이 Nginx를 사용하고 있습니다.

주요 특징

1. 높은 성능과 확장성

Nginx는 비동기 이벤트 기반(Event-Driven) 아키텍처를 채택하여 적은 수의 워커 프로세스로 수만 개의 동시 연결을 처리할 수 있습니다. Apache의 프로세스/스레드 기반 모델과 달리, 각 연결마다 새로운 프로세스나 스레드를 생성하지 않아 메모리 효율이 뛰어납니다.

2. 낮은 리소스 사용

동일한 트래픽을 처리할 때 Apache 대비 메모리 사용량이 현저히 낮습니다. 이는 클라우드 환경에서 비용 절감으로 직결되며, 제한된 리소스를 가진 서버에서도 우수한 성능을 발휘합니다.

3. 확장성과 모듈화

다양한 모듈을 통해 기능을 확장할 수 있습니다. 기본 제공되는 코어 모듈 외에도 서드파티 모듈을 컴파일하여 사용할 수 있으며, Nginx Plus(상용 버전)에서는 더욱 강력한 기능을 제공합니다.

4. 무중단 운영

설정 파일을 수정한 후 nginx -s reload 명령으로 서비스 중단 없이 새로운 설정을 적용할 수 있습니다. 또한 바이너리 업그레이드도 무중단으로 가능하여 24/7 운영이 필요한 환경에 적합합니다.

5. 다양한 역할 수행

  • 웹 서버: 정적 파일(HTML, CSS, JavaScript, 이미지)을 매우 빠르게 제공
  • 리버스 프록시: 백엔드 애플리케이션 서버 앞단에서 요청을 중계
  • 로드 밸런서: 여러 백엔드 서버로 트래픽을 분산
  • HTTP 캐시: 응답을 캐싱하여 백엔드 부하 감소
  • API Gateway: 마이크로서비스 아키텍처에서 진입점 역할

Nginx vs Apache 비교

웹 서버를 선택할 때 가장 많이 비교되는 두 솔루션이 Nginx와 Apache입니다. 각각의 장단점을 이해하면 프로젝트에 적합한 웹 서버를 선택하는 데 도움이 됩니다.

항목 Nginx Apache
아키텍처 이벤트 기반 (비동기) 프로세스/스레드 기반 (동기)
동시 접속 처리 우수 보통
메모리 사용량 낮음 높음
정적 콘텐츠 제공 매우 빠름 보통
동적 콘텐츠 처리 프록시 필요 자체 처리 가능
설정 파일 간결함 복잡함 (.htaccess 지원)
모듈 시스템 정적 컴파일 동적 로딩

언제 Nginx를 선택해야 할까?

  • 고트래픽 웹사이트: 동시 접속자가 많은 경우 (수천~수만 명)
  • 정적 파일 서빙: 이미지, CSS, JavaScript 등 정적 자원 제공이 주된 목적
  • 리버스 프록시: Node.js, Python, Java 등의 애플리케이션 서버 앞단
  • 로드 밸런싱: 여러 백엔드 서버로 트래픽 분산 필요
  • 클라우드 환경: 리소스 효율이 중요하고 비용 절감 필요

언제 Apache를 선택해야 할까?

  • 레거시 애플리케이션: .htaccess 기반 설정에 의존하는 경우
  • PHP 애플리케이션: mod_php를 통한 PHP 직접 처리가 필요한 경우
  • 다양한 모듈 활용: 동적 모듈 로딩으로 유연한 기능 확장이 필요
  • 공유 호스팅: 디렉토리별 권한 제어와 사용자별 설정이 중요
  • 완전한 호환성: 오래된 웹 애플리케이션과의 호환성 우선

실무에서의 선택

많은 현대적인 웹 아키텍처에서는 Nginx를 프론트엔드 리버스 프록시로, Apache나 다른 애플리케이션 서버를 백엔드로 함께 사용합니다. 이런 구성으로 Nginx의 빠른 정적 파일 서빙과 효율적인 연결 처리, Apache의 동적 콘텐츠 처리 능력을 모두 활용할 수 있습니다.

Nginx 설치

Ubuntu/Debian

sudo apt update
sudo apt install nginx

# Nginx 시작
sudo systemctl start nginx

# 부팅 시 자동 시작 설정
sudo systemctl enable nginx

# 상태 확인
sudo systemctl status nginx

CentOS/RHEL

sudo yum install epel-release
sudo yum install nginx

sudo systemctl start nginx
sudo systemctl enable nginx

macOS (Homebrew)

brew install nginx

# Nginx 시작
brew services start nginx

Docker

docker run -d -p 80:80 --name nginx nginx:latest

Nginx 기본 구조

디렉토리 구조

/etc/nginx/
├── nginx.conf # 메인 설정 파일
├── sites-available/ # 사용 가능한 사이트 설정
├── sites-enabled/ # 활성화된 사이트 설정 (심볼릭 링크)
├── conf.d/ # 추가 설정 파일
├── snippets/ # 재사용 가능한 설정 조각
└── modules-enabled/ # 활성화된 모듈

/var/log/nginx/ # 로그 디렉토리
├── access.log # 접근 로그
└── error.log # 에러 로그

/var/www/html/ # 기본 웹 루트 디렉토리

nginx.conf 기본 구조

# 워커 프로세스를 실행할 사용자
user www-data;

# 워커 프로세스 수 (auto = CPU 코어 수)
worker_processes auto;

# PID 파일 위치
pid /run/nginx.pid;

# 이벤트 블록: 연결 처리 방식 설정
events {
# 워커 프로세스당 최대 동시 연결 수
worker_connections 1024;

# 연결 처리 방법 (epoll은 Linux에서 효율적)
use epoll;
}

# HTTP 블록: 웹 서버 설정
http {
# 기본 설정
include /etc/nginx/mime.types;
default_type application/octet-stream;

# 로그 형식
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

# 접근 로그 설정
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;

# 성능 최적화
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;

# Gzip 압축
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss;

# 가상 호스트 설정 포함
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

핵심 설정 가이드

1. 기본 웹 서버 설정

server {
# 포트 설정
listen 80;
listen [::]:80; # IPv6

# 서버 이름 (도메인)
server_name example.com www.example.com;

# 문서 루트
root /var/www/example.com;
index index.html index.htm;

# 로그 파일
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;

# 기본 location 블록
location / {
try_files $uri $uri/ =404;
}
}

2. HTTPS/SSL 설정

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;

server_name example.com;

# SSL 인증서
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;

# SSL 프로토콜 및 암호화
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;

# HSTS (HTTP Strict Transport Security)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

root /var/www/example.com;
index index.html;
}

# HTTP를 HTTPS로 리다이렉트
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}

3. 리버스 프록시 설정

server {
listen 80;
server_name api.example.com;

location / {
# 백엔드 서버로 프록시
proxy_pass http://localhost: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_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

# 버퍼 설정
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
}

4. 로드 밸런싱 설정

# Upstream 블록: 백엔드 서버 그룹 정의
upstream backend {
# 로드 밸런싱 방법
# - 기본: 라운드 로빈
# - least_conn: 최소 연결
# - ip_hash: 클라이언트 IP 기반
# - random: 랜덤
least_conn;

# 백엔드 서버 목록
server backend1.example.com:8080 weight=3;
server backend2.example.com:8080 weight=2;
server backend3.example.com:8080 backup; # 백업 서버

# 헬스 체크 (Nginx Plus 기능)
# server backend4.example.com:8080 max_fails=3 fail_timeout=30s;
}

server {
listen 80;
server_name example.com;

location / {
proxy_pass http://backend;
proxy_next_upstream error timeout invalid_header http_500;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

로드 밸런싱 알고리즘

# 1. 라운드 로빈 (기본값)
upstream backend {
server srv1.example.com;
server srv2.example.com;
}

# 2. 가중치 기반 라운드 로빈
upstream backend {
server srv1.example.com weight=3;
server srv2.example.com weight=1;
}

# 3. 최소 연결
upstream backend {
least_conn;
server srv1.example.com;
server srv2.example.com;
}

# 4. IP 해시 (세션 유지)
upstream backend {
ip_hash;
server srv1.example.com;
server srv2.example.com;
}

# 5. 랜덤
upstream backend {
random;
server srv1.example.com;
server srv2.example.com;
}

5. 정적 파일 서빙 최적화

server {
listen 80;
server_name static.example.com;

root /var/www/static;

# 정적 파일 캐싱
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}

# Gzip 압축
location ~* \.(css|js|json|xml)$ {
gzip_static on;
expires 1y;
add_header Cache-Control "public";
}
}

6. 캐싱 설정

# 캐시 존 정의
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=my_cache:10m
max_size=1g
inactive=60m
use_temp_path=off;

server {
listen 80;
server_name example.com;

location / {
proxy_pass http://backend;

# 캐시 활성화
proxy_cache my_cache;

# 캐시 키
proxy_cache_key "$scheme$request_method$host$request_uri";

# 캐시 유효 시간
proxy_cache_valid 200 60m;
proxy_cache_valid 404 10m;

# 캐시 우회 조건
proxy_cache_bypass $cookie_nocache $arg_nocache;

# 캐시 상태 헤더 추가
add_header X-Cache-Status $upstream_cache_status;
}
}

7. WebSocket 프록시

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

server {
listen 80;
server_name ws.example.com;

location /ws {
proxy_pass http://backend;

# WebSocket 지원
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;

# 타임아웃 설정 (WebSocket은 긴 연결)
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}

8. Rate Limiting (요청 제한)

# Zone 정의: 초당 10개 요청으로 제한
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;

server {
listen 80;
server_name example.com;

location /api/ {
# Rate limiting 적용
limit_req zone=mylimit burst=20 nodelay;

# 429 에러 시 커스텀 응답
limit_req_status 429;

proxy_pass http://backend;
}
}

9. 보안 헤더 설정

server {
listen 443 ssl http2;
server_name example.com;

# 보안 헤더
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' https: data: 'unsafe-inline' 'unsafe-eval';" always;

# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

location / {
root /var/www/example.com;
index index.html;
}
}

10. 리다이렉트 및 Rewrite

server {
listen 80;
server_name example.com;

# 단순 리다이렉트
location /old-page {
return 301 /new-page;
}

# 도메인 리다이렉트
location / {
return 301 https://newdomain.com$request_uri;
}

# Rewrite 규칙
location /users/ {
rewrite ^/users/(.*)$ /user-profile?id=$1 last;
}

# 조건부 리다이렉트
if ($host != 'example.com') {
return 301 https://example.com$request_uri;
}
}

SPA (Single Page Application) 설정

React/Vue/Angular 배포

server {
listen 80;
server_name app.example.com;

root /var/www/app/dist;
index index.html;

# SPA 라우팅 지원
location / {
try_files $uri $uri/ /index.html;
}

# API 요청은 백엔드로 프록시
location /api {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

# 정적 자산 캐싱
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}

마이크로서비스 라우팅

server {
listen 80;
server_name api.example.com;

# Auth Service
location /auth {
proxy_pass http://auth-service:8001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

# User Service
location /users {
proxy_pass http://user-service:8002;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

# Order Service
location /orders {
proxy_pass http://order-service:8003;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

# Payment Service
location /payments {
proxy_pass http://payment-service:8004;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}

성능 최적화

1. Worker 프로세스 최적화

# CPU 코어 수에 맞게 설정
worker_processes auto;

# 워커 프로세스를 특정 CPU에 바인딩
worker_cpu_affinity auto;

# 워커 프로세스당 파일 디스크립터 수
worker_rlimit_nofile 65535;

events {
# 워커당 연결 수 증가
worker_connections 4096;

# 동시에 여러 연결 수락
multi_accept on;

# epoll 사용 (Linux)
use epoll;
}

2. 버퍼 및 타임아웃 최적화

http {
# 클라이언트 요청 버퍼
client_body_buffer_size 128k;
client_max_body_size 100M;
client_header_buffer_size 1k;
large_client_header_buffers 4 16k;

# 타임아웃 설정
client_body_timeout 12;
client_header_timeout 12;
keepalive_timeout 15;
send_timeout 10;

# 파일 전송 최적화
sendfile on;
tcp_nopush on;
tcp_nodelay on;
}

3. 압축 최적화

http {
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_disable "msie6";

gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/rss+xml
font/truetype
font/opentype
application/vnd.ms-fontobject
image/svg+xml;
}

모니터링 및 로깅

1. 액세스 로그 커스터마이징

http {
# 상세한 로그 형식 정의
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';

# JSON 로그 형식
log_format json escape=json '{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"request":"$request",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time"'
'}';

access_log /var/log/nginx/access.log detailed;
}

2. Status 페이지 활성화

server {
listen 8080;
server_name localhost;

location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}

Docker와 Nginx

Dockerfile 예제

FROM nginx:alpine

# 커스텀 설정 파일 복사
COPY nginx.conf /etc/nginx/nginx.conf
COPY conf.d/ /etc/nginx/conf.d/

# 정적 파일 복사
COPY dist/ /usr/share/nginx/html/

# 포트 노출
EXPOSE 80 443

CMD ["nginx", "-g", "daemon off;"]

docker-compose.yml 예제

version: '3.8'

services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./conf.d:/etc/nginx/conf.d:ro
- ./ssl:/etc/nginx/ssl:ro
- ./html:/usr/share/nginx/html:ro
- ./logs:/var/log/nginx
networks:
- app-network
restart: unless-stopped

app:
image: myapp:latest
networks:
- app-network

networks:
app-network:
driver: bridge

Nginx 관리 명령어

기본 명령어

# 설정 테스트
nginx -t

# 설정 리로드 (무중단)
nginx -s reload

# Nginx 중지
nginx -s stop

# Graceful 종료
nginx -s quit

# Nginx 재시작
systemctl restart nginx

# 버전 확인
nginx -v
nginx -V # 상세 정보 포함

# 설정 파일 경로 확인
nginx -t 2>&1 | grep "configuration file"

로그 관리

# 로그 로테이션 신호 전송
nginx -s reopen

# 실시간 로그 확인
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log

# 에러 로그 필터링
grep "error" /var/log/nginx/error.log

# 특정 IP 요청 확인
grep "192.168.1.100" /var/log/nginx/access.log

트러블슈팅

1. 502 Bad Gateway

원인:

  • 백엔드 서버가 다운되었거나 응답하지 않음
  • 방화벽이 연결을 차단
  • SELinux가 프록시 연결을 차단 (CentOS/RHEL)

해결:

# 백엔드 서버 상태 확인
curl http://localhost:3000

# SELinux 문제 해결
setsebool -P httpd_can_network_connect 1

# 방화벽 확인
iptables -L -n

2. 413 Request Entity Too Large

원인: 업로드 파일 크기 제한 초과

해결:

http {
client_max_body_size 100M;
}

3. 504 Gateway Timeout

원인: 백엔드 서버 응답 시간 초과

해결:

location / {
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
}

4. 너무 많은 리다이렉트

원인: 무한 리다이렉트 루프

해결:

# X-Forwarded-Proto 헤더 확인
if ($http_x_forwarded_proto = "https") {
set $https_redirect 0;
}

if ($https_redirect = 1) {
return 301 https://$server_name$request_uri;
}

보안 베스트 프랙티스

1. 기본 보안 설정

http {
# 서버 버전 숨기기
server_tokens off;

# 불필요한 메서드 차단
if ($request_method !~ ^(GET|POST|PUT|DELETE|HEAD)$) {
return 405;
}

# 특정 파일 접근 차단
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}

# 백업 파일 접근 차단
location ~ ~$ {
deny all;
}
}

2. DDoS 방어

# 연결 제한
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;

server {
# 동시 연결 제한
limit_conn conn_limit 10;

# 요청 빈도 제한
limit_req zone=req_limit burst=20 nodelay;
}

3. IP 화이트리스트/블랙리스트

# 특정 IP만 허용
location /admin {
allow 192.168.1.0/24;
allow 10.0.0.1;
deny all;
}

# GeoIP 기반 차단 (모듈 필요)
if ($geoip_country_code ~ (CN|RU)) {
return 403;
}

4. Basic Auth 설정

# htpasswd 파일 생성
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd username
location /admin {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
}

Let’s Encrypt SSL 인증서

Certbot 사용

# Certbot 설치
sudo apt install certbot python3-certbot-nginx

# 인증서 발급
sudo certbot --nginx -d example.com -d www.example.com

# 자동 갱신 테스트
sudo certbot renew --dry-run

# Cron 작업으로 자동 갱신
0 3 * * * certbot renew --quiet

참고 자료

Share