본문으로 건너뛰기
Previous
Next
Nginx 완벽 가이드 | 리버스 프록시·로드 밸런싱·SSL·캐싱·성능 최적화

Nginx 완벽 가이드 | 리버스 프록시·로드 밸런싱·SSL·캐싱·성능 최적화

Nginx 완벽 가이드 | 리버스 프록시·로드 밸런싱·SSL·캐싱·성능 최적화

이 글의 핵심

Nginx 완벽 가이드에 대해 정리한 개발 블로그 글입니다. Nginx로 고성능 웹 서버를 구축하는 완벽 가이드입니다. 내부 구조(이벤트 루프, 마스터/워커, 설정 상속, 로드 밸런싱 알고리즘, 프로덕션 패턴)를 먼저 짚은 뒤, 설치·리버스 프록시·로드… 개념과 예제 코드를 단계적으로 다루며, 실무·학습에 참고할 수 있도록 구성했습니다. 관련 키워드: Nginx, Web Server,…

이 글의 핵심

Nginx로 고성능 웹 서버를 구축하는 완벽 가이드입니다. 내부 구조(이벤트 루프, 마스터/워커, 설정 상속, 로드 밸런싱 알고리즘, 프로덕션 패턴)를 먼저 짚은 뒤, 설치·리버스 프록시·로드 밸런싱·SSL/TLS·캐싱·성능 최적화를 실전 예제로 정리했습니다.

실무 경험 공유: Apache를 Nginx로 교체하면서, 동시 접속자 처리량을 5배 향상시키고 메모리 사용량을 60% 줄인 경험을 공유합니다.

들어가며: “웹 서버가 느려요” 같은 말

예전에도 그랬는데, 로드 밸런싱은 문서엔 upstream 블록 몇 줄이면 끝인 것처럼 보이는데, 운영에선 “왜 이 노드만 터지지?” 쪽이 더 잦아요. 아래 시나리오는 제가 팀에서 실제로 자주 봤던 패턴들입니다.

실무에서 자주 터지는 질문들

1. 동시 접속이 많으면 느려질 때 — Apache 쪽은 대충 1,000 concurrent 근처에서 힘들어하는 경우가 많고, Nginx는 그보다 훨씬 위까지 올리기 쉬운 편이에요(환경·워커·TLS 따라 다름).

2. 정적 파일이 앱 서버를 탈 때 — JS/CSS/이미지까지 Node나 스프링이 직접 먹이면, 그건 그냥 아깝습니다. Nginx에 붙이면 대부분 체감이 달라져요.

3. SSL이 귀찮을 때 — Let’s Encrypt+Certbot 조합이면 “한 번 잡고 가면” 이후는 갱신만 챙기면 돼서, 수작업으로 인증서 갈아끼우던 시절이랑 비교가 안 돼요.

1. Nginx란?

핵심 특징

Nginx는 고성능 웹 서버이자 리버스 프록시입니다. 주요 사용 사례:

  • 웹 서버: 정적 파일 서빙
  • 리버스 프록시: 백엔드 앞단
  • 로드 밸런서: 트래픽 분산
  • SSL 종료: HTTPS 처리
  • 캐싱: 응답 캐싱 성능:
  • 동시 접속: 10,000+ connections
  • 메모리: 2.5MB per worker

2. 내부 구조: 이벤트 루프·워커·설정 상속·로드 밸런싱·프로덕션 패턴

실무에서 Nginx를 “설정만 복사”하는 수준에 머무르면, 튜닝 한계와 장애 대응이 어렵습니다. 이 절에서는 프로세스 모델, 이벤트 기반 I/O, 설정 컨텍스트 상속, 업스트림 알고리즘, 프로덕션 운영 패턴을 한 번에 정리합니다.

이벤트 기반 아키텍처

전통적인 프로세스/스레드 기반 웹 서버는 요청마다 자원을 할당합니다. 동시 접속이 늘면 컨텍스트 스위칭과 메모리 오버헤드가 커집니다. Nginx 워커는 이벤트 루프 위에서 동작하며, 수많은 커넥션을 소수의 프로세스로 처리합니다.

  • 이벤트 모듈: Linux에서는 epoll, BSD/macOS에서는 kqueue를 사용합니다. 설정의 events { use epoll; }는 이 경로를 명시합니다(플랫폼에 따라 생략 가능).
  • 한 워커, 다수 커넥션: 각 워커는 “준비된 소켓”만 처리합니다. 읽기/쓰기가 블로킹되지 않도록 논블로킹 소켓과 커널 이벤트 알림에 의존합니다.
  • sendfile: 디스크에서 소켓으로 데이터를 옮길 때 사용자 공간 복사를 줄이는 경로로, 정적 파일 서빙에서 CPU와 메모리 대역폭을 절약합니다.
  • worker_connections: 워커당 동시에 처리할 수 있는 커넥션 수 상한입니다. worker_processes × worker_connections가 이론상의 동시 접속 상한에 가깝고, 실제로는 파일 디스크립터 제한(ulimit -n)과 맞춰야 합니다.

이 구조가 C10K(만 단위 동시 접속) 문제에 대응하기 쉬운 이유입니다. 다만 CPU 집약적인 TLS 핸드셰이크나 큰 본문 처리는 워커에 부하를 집중시키므로, 이 경우 워커 수·하드웨어 스펙·TLS 세션 재개 등을 함께 봐야 합니다.

워커 프로세스 모델

Nginx는 master 프로세스worker 프로세스로 나뉩니다.

  • Master: 설정 파일을 읽고, 워커를 포크합니다. 포트 바인딩·권한 상승(예: 80번 포트)은 보통 마스터가 담당하고, 워커는 연결을 처리합니다.
  • Worker: 실제 HTTP 요청 처리, 리버스 프록시, 캐시 I/O 등이 여기서 수행됩니다. worker_processes auto는 CPU 코어 수에 맞추는 일반적인 선택입니다.
  • 무중단 리로드: nginx -s reload 또는 SIGHUP새 설정으로 워커를 순차 교체하는 graceful reload입니다. 진행 중인 요청을 끊지 않도록 설계되어 있으나, 백엔드 연결·타임아웃 설정과 함께 봐야 합니다.
  • 종료: SIGTERM은 graceful shutdown, SIGQUIT도 graceful, SIGKILL은 즉시 종료입니다. 배포 스크립트와 systemd 유닛이 어떤 시그널을 쓰는지 확인하는 것이 좋습니다.

워커는 서로 메모리를 공유하지 않습니다. 공유 캐시가 필요하면 OS 페이지 캐시에 의존하거나, Redis 등 외부 캐시를 쓰는 패턴이 일반적입니다.

설정 상속과 병합

Nginx 설정은 컨텍스트(context) 트리입니다: mainevents, httpserverlocation(또는 upstream 등).

  • 상속: http 블록의 gzip on은 하위 server에 기본 적용됩니다. 하위에서 다시 지정하면 해당 블록에서 덮어씁니다.
  • include: 파일을 분할해 포함합니다. 스니펫을 재사용할 때 실제 병합 결과는 한 덩어리의 설정 트리로 해석됩니다.
  • location 매칭 순서: 정확 일치 = → 가장 긴 접두사 → ^~가 있으면 정규식 생략 → 순서대로 정규식 ~ / ~*. 복잡한 if 남용은 디버깅을 어렵게 하므로, 가능하면 map과 분기 location으로 푸는 편이 안전합니다.
  • default_server: 같은 listen에 여러 server_name이 있을 때 기본으로 택할 가상 호스트를 지정합니다.

이해가 부족하면 “상위에서 켠 옵션이 하위에 어떻게 남는지” 추적이 어려워지므로, 테스트 도구(nginx -t)와 단계적 include가 중요합니다.

한밤중에 p95가 갑자기 찢어졌을 때 (로드 밸런싱 이슈 썰)

이건 예전 팀이었는데, API 클러스터 세 대를 그냥 기본 upstream(라운드 로빈)에 올려둔 상태에서 장애가 났어요. 한 대가 배포 직후 콜드 스타트+GC로 응답이 느려졌는데, 트래픽은 1/3씩 “공정하게” 들어가니까, 사용자 입장에선 세 명 중 한 명꼴로 랜덤하게 느려지는 그림이 된 거죠. 대시보드엔 “평균은 괜찮은데 p95·p99만 이상하다”로만 보이고, 처음엔 DB나 네트워크를 의심했어요.

결국 least_conn으로 바꾸고, max_fails/fail_timeout으로 죽은 것 같은 노드를 빼는 쪽을 잡고 나서야 그래프가 정상 궤도로 돌아왔습니다. 그때 “기본이 라운드 로빈이니까 일단 쓰자”가 밤을 길게 만든 건 맞다고 봐요. (NAT 뒤에서 ip_hash만 믿고 세션을 로컬에 싹 박아둔 케이스도 비슷한 밤이었고요. IP가 바뀌는 순간 로그인 풀리는 민원이 쏟아짐.)

솔직히 라운드로빈만 쓰지 마세요. “모든 백엔드가 똑같이 건강하고, 요청마다 비용이 비슷할 때”라는 가정이 맞을 때만 예쁘게 돌아가요. 실서비스에선 그 가정이 틀리는 날이 꼭 옵니다.

로드 밸런싱 알고리즘 심화

upstream에서 고르는 방식은 트래픽이 아니라 “부하”를 어떻게 나눌지 문제예요. 표로 정리하려다 뺐으니, 제 기준으로만 말해볼게요.

  • 기본(라운드 로빈): 그냥 돌아가며 나눔. “균등”이 아니라 “순서”예요. 스펙·지연·큐 길이가 다르면 느린 노드한테도 똑같이 밀어 넣습니다.
  • least_conn: 지금 붙어 있는 커넥션이 가장 적은 쪽. 응답 시간 들쭉날쭉하거나, 웹소켓·스트림 같이 오래 붙는 연결이 많으면 라운드 로빈보다 훨씬 먹히는 경우가 많아요. 저는 stateless HTTP API에도 “일단 이쪽부터 본다” 쪽이면 여기를 자주 씁니다.
  • ip_hash: IP 기준으로 백엔드 고정. 레거시에 세션이 앱 메모리에 박혀 있을 때 임시 방편으로 쓰는 느낌. 모바일·회사망·캐리어 NAT이면 “갑자기 IP가 바뀜” 이슈를 각오해야 해요. 가능하면 세션을 밖(Redis 등)으로 빼는 쪽이 낫다고 봅니다.
  • hash ... consistent: 키(예: URI)로 샤딩할 때. 캐시 노드 앞이면 “살짝 옮겨도 전부 재배치”보다 링 해시가 덜 아픈 경우가 있어요.
  • random two + least_conn (1.15+): 후보 둘 뽑고 덜 붙은 쪽. 트래픽 흔들릴 때 분산이 괜찮다는 얘기를 들은 적은 있는데, 팀이 익숙한 건 least_conn 쪽이었어요.

가중치 weight, down, backup, max_fails/fail_timeout은 “설정”이라기보다 운영에 가깝고, 액티브 헬스는 오픈소스 Nginx만으로는 아쉬울 때가 많아서 Nginx Plus·클라우드 LB·메시가 같이 언급됩니다.

upstream api {
    least_conn;
    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 backup;
}

upstream cache_nodes {
    hash $request_uri consistent;
    server 10.0.0.10:11211;
    server 10.0.0.11:11211;
}

프로덕션 Nginx 패턴

  • TLS 종료 + 업스트림 keepalive: 클라이언트와는 HTTPS, 내부는 HTTP로 두되 업스트림 커넥션 풀(proxy_http_version 1.1, proxy_set_header Connection "", keepalive 지시문 in upstream)으로 백엔드의 TCP/TLS 부담을 줄입니다.
  • proxy_next_upstream: 특정 오류·타임아웃 시 다른 업스트림으로 재시도할지 정합니다. 중복 전송이 위험한 메서드(비멱등 POST 등)는 non_idempotent와 함께 신중히 설정합니다.
  • 레이트 리밋: limit_req_zone / limit_req로 API 남용을 막고, limit_conn으로 동일 IP·키의 동시 커넥션을 제한합니다.
  • 관측 가능성: stub_status on(모듈 포함 빌드 시) 또는 exporter, 구조화된 액세스 로그(log_format json 등)로 지연·캐시 히트율을 추적합니다.
  • 배포: nginx -t로 문법 검증 후 reload. 설정 드리프트를 막으려면 Git으로 관리하고 IaC와 연동하는 것이 좋습니다.

아래 실습 절(로드 밸런싱, SSL, 캐싱)의 예제는 이 절의 개념을 구체 설정으로 옮긴 것으로 보면 됩니다.


3. 설치

Ubuntu

sudo apt update
sudo apt install nginx
# 시작
sudo systemctl start nginx
# 부팅 시 자동 시작
sudo systemctl enable nginx
# 상태 확인
sudo systemctl status nginx

Docker

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

4. 기본 설정

nginx.conf

# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
    worker_connections 1024;
}
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"';
    access_log /var/log/nginx/access.log main;
    sendfile on;
    tcp_nopush on;
    keepalive_timeout 65;
    gzip on;
    include /etc/nginx/conf.d/*.conf;
}

5. 정적 파일 서빙

# /etc/nginx/conf.d/default.conf
server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    index index.html;
    location / {
        try_files $uri $uri/ =404;
    }
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

6. 리버스 프록시

기본 설정

server {
    listen 80;
    server_name api.example.com;
    location / {
        proxy_pass http://localhost:8000;
        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

location /ws {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

7. 로드 밸런싱

2절에서 왜 “라운드로빈만”이 위험한지는 썰로 대충 봤고요. 여기선 문법만 빠르게. 복붙하다가 upstream 기본이 라운드 로빈인 거 까먹지 말고, 최소한 max_fails는 같이 봐주세요. “됐다” 하고 자러 가기 전에 nginx -t도 잊지 말고요.

Round Robin (기본) — “그냥 기본”이라 믿지 말기

upstream backend {
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}
server {
    listen 80;
    location / {
        proxy_pass http://backend;
    }
}

Least Connections

upstream backend {
    least_conn;
    server backend1.example.com;
    server backend2.example.com;
}

IP Hash (세션 유지)

upstream backend {
    ip_hash;
    server backend1.example.com;
    server backend2.example.com;
}

가중치

upstream backend {
    server backend1.example.com weight=3;
    server backend2.example.com weight=2;
    server backend3.example.com weight=1;
}

일관성 해시 (hash + consistent)

노드가 늘거나 줄어도 키 재배치를 상대적으로 줄이고 싶을 때(캐시 샤딩 등) 사용합니다.

upstream shard {
    hash $request_uri consistent;
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

random two (Open Source 1.15+)

무작위로 두 백엔드를 고른 뒤, 그중 부하가 적은 쪽으로 보내는 방식입니다.

upstream backend {
    random two least_conn;
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}

8. SSL/TLS

Let’s Encrypt

# Certbot 설치
sudo apt install certbot python3-certbot-nginx
# 인증서 발급
sudo certbot --nginx -d example.com -d www.example.com
# 자동 갱신
sudo certbot renew --dry-run

SSL 설정

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;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    location / {
        proxy_pass http://localhost:8000;
    }
}
# HTTP → HTTPS 리다이렉트
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

9. 캐싱

Proxy Cache

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m;
server {
    location / {
        proxy_cache my_cache;
        proxy_cache_valid 200 60m;
        proxy_cache_valid 404 1m;
        proxy_cache_bypass $http_cache_control;
        add_header X-Cache-Status $upstream_cache_status;
        proxy_pass http://backend;
    }
}

10. 성능 최적화

Gzip 압축

gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript 
           application/json application/javascript application/xml+rss;

연결 최적화

# Worker 프로세스 수
worker_processes auto;
# 연결 수
events {
    worker_connections 2048;
    use epoll;
}
# Keepalive
keepalive_timeout 65;
keepalive_requests 100;

업스트림 Keepalive (백엔드 커넥션 풀)

리버스 프록시에서 백엔드로 매 요청마다 새 TCP 연결을 열면 지연과 포트 고갈이 커집니다. HTTP/1.1 업스트림 풀을 쓰려면 upstreamkeepalive를 두고, proxy 쪽에서 Connection 헤더를 비웁니다.

upstream api {
    server 127.0.0.1:8080;
    keepalive 64;
}
server {
    location / {
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_pass http://api;
    }
}

버퍼 크기

client_body_buffer_size 10K;
client_header_buffer_size 1k;
client_max_body_size 8m;
large_client_header_buffers 2 1k;

11. 실전 예제: 풀스택 앱

다음은 nginx 예제 코드입니다.

# /etc/nginx/conf.d/app.conf
upstream frontend {
    server localhost:3000;
}
upstream backend {
    server localhost:8000;
    server localhost:8001;
    server localhost:8002;
}
# HTTP → HTTPS
server {
    listen 80;
    server_name myapp.com www.myapp.com;
    return 301 https://$server_name$request_uri;
}
# HTTPS
server {
    listen 443 ssl http2;
    server_name myapp.com www.myapp.com;
    ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
    # Frontend (React)
    location / {
        proxy_pass http://frontend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
    # Backend API
    location /api {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        
        # CORS
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
    }
    # 정적 파일
    location /static {
        alias /var/www/static;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

취업·면접과 연결하기

리버스 프록시·SSL·캐싱을 운영 경험으로 말로 풀 수 있게 정리해 두면 인프라·백엔드 면접에서 유리합니다. 기술 면접 완벽 대비 가이드의 CS·시스템 질문 흐름과, 이력서에 어떻게 쓸지는 개발자 이력서·서류·면접 가이드를 참고하세요.

정리 및 체크리스트

핵심 요약

  • Nginx: 고성능 웹 서버
  • 리버스 프록시: 백엔드 앞단
  • 로드 밸런싱: 트래픽 분산
  • SSL/TLS: HTTPS 지원
  • 캐싱: 응답 캐싱
  • 성능: 10,000+ 동시 접속

프로덕션 체크리스트

  • Nginx 설치
  • 리버스 프록시 설정
  • 로드 밸런싱 구성
  • SSL 인증서 발급
  • 캐싱 설정
  • 성능 최적화
  • 모니터링 설정

같이 보면 좋은 글


이 글에서 다루는 키워드

Nginx, Web Server, Reverse Proxy, Load Balancing, SSL, Performance, DevOps

내부 동작과 핵심 메커니즘

이 글의 주제는 「Nginx 완벽 가이드 | 리버스 프록시·로드 밸런싱·SSL·캐싱·성능 최적화」입니다. 앞선 튜토리얼을 구현·런타임 관점에서 다시 압축합니다. 구성 요소 간 책임 분리와 관측 가능한 지점을 기준으로 “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)·동시성이 어디서 터지는가”를 한 장면으로 그리면 장애 분석이 빨라집니다.

처리 파이프라인(개념도)

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]

경계에서의 지연·실패(시퀀스 관점)

sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(프로세스·런타임·게이트웨이)
  participant D as 의존성(외부 API·DB·큐)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)

알고리즘·프로토콜·리소스 관점 체크포인트

  • 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(버퍼 경계, 프로토콜 상태, 트랜잭션 격리, 파일 디스크립터 상한)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 동일 입력에 동일 출력이 보장되는 순수 층과, 시간·네트워크·스레드 스케줄에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합, GC·할당, 캐시 미스처럼 누적 비용을 의심 목록에 넣습니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때(소켓 버퍼, 큐 깊이, 스트림) 어디서 어떤 신호로 속도를 줄일지 정의합니다.

프로덕션 운영 패턴

실서비스에서는 기능과 함께 관측·배포·보안·비용·규제가 동시에 요구됩니다. 표로 박아두기엔 케이스가 너무 다양해서, 제가 체크리스트 쓸 때 머릿속으로 도는 질문만 풀어 쓸게요.

  • 관측성 — 상관 ID 있나요, p95/p99·에러율·업스트림 타임아웃이 한 화면에 잡히나요. (LB만 건드렸는데 p99만 괴이하게 튀는 그래프, 위에서 썬 것처럼 라운빈+한 노드 느림 조합이 꽤 흔해요.)
  • 안전성 — 경계마다 인증·입력검증·감사 로그가 일관적인지. 프록시 뒤 X-Forwarded-For 믿는 범위도 여기.
  • 신뢰성 — 비멱등 POST에 proxy_next_upstream 잘못 열어둔 것 같진 않은지, DLQ·서킷·백오프는요.
  • 성능 — 캐시, 커넥션 풀(위에 keepalive), DB N+1… Nginx는 여기 “막” 중 하나고, 병목은 대개 그 뒤에 있어요.
  • 배포 — 롤백 루트, 카나리, 마이그레이션 호환, 피처 플래그. reload가 된다고 무중단이 항상 보장은 아님(타임아웃·롱풀 연결).
  • 용량 — fd 한도, Worker×connection, 피크 때 디스크 I/O. 스테이징은 데이터 양·RTT·동시성을 프로덕션에 가깝게: 없으면 “로드밸런서는 됐는데 DB가 터짐”만 재현돼요.

확장 예시: 엔드투엔드 미니 시나리오

「Nginx 완벽 가이드 | 리버스 프록시·로드 밸런싱·SSL·캐싱·성능 최적화」을 실제 배포·운영 흐름으로 옮긴 체크리스트형 시나리오입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드 표를 API 또는 이벤트 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 한 화면(로그+메트릭+트레이스)에서 추적한다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지(또는 피처 플래그) 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값이 기대 범위인지 본다.

의사코드 스케치(프레임워크 무관)

handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)        // 경계에서 거절
  authorize(validated, ctx)                  // 권한·테넌트
  result = domainCore(validated)             // 순수에 가까운 규칙
  persistOrEmit(result, idempotentKey)       // I/O: 멱등·재시도 정책
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

표 대신, 증상별로 “나였으면 이렇게 좁힌다” 느낌으로.

  • 간헐 실패 — 타임아웃, DNS, 외부 API, 라운빈으로 느린 백엔드만 가끔 밟는지. 상관 ID로 한 줄로 엮고, 스테이징에서 재현 스크립트부터.
  • 느려짐 — Nginx 전에 APM/프로파일로 앱·DB 쪽 먼저. LB가 least_conn이면 “한 대만” 병목인지, 아니면 전부 느린지 구분이 쉬워져요.
  • 메모리만 계속 쑥 — 캐시 상한, 웹소켓·구독 누수, FD. ss/lsof로 열린 것부터.
  • 배포만 터짐 — env, lockfile, 이미지 태그. nginx -t는 CI에 넣어두면 밤이 짧아짐.
  • 로컬에선 되는데 서버만 — 프로필·리전·시크릿. Nginx include 경로가 서버에만 다른 경우도 있어요(제가 sites-enabled만 맞췄다고 착각한 적 있음).
  • 데이터가 어긋남proxy_next_upstream이 POST를 두 번 보낸 건 아닌지, 멱등 키·캐시 무효화.

권장 순서: (1) 최소 재현 (2) 어제/오늘 뭐 올렸는지 (3) env 차이 (4) 메트릭/로그로 가설 (5) 고치고 부하·회귀. 급하면 2번부터.

자주 묻는 질문 (FAQ)

Q. upstream 기본이 라운드 로빈인데, 그냥 두면 안 되나요?

A. “안 된다”기보다 그만 두면 밤이 길 수 있다 쪽이에요. 백엔드가 전부 똑같이 건강하다는 보장이 없으면, 기본값은 느린 노드한테도 똑같이 밀어 넣습니다. least_conn이나 weight, max_fails부터 같이 보라는 말이 그래서 나옵니다.

Q. Nginx vs Apache, 어떤 게 나은가요?

A. Nginx가 더 빠르고 메모리 효율적입니다. 대부분의 경우 Nginx를 권장합니다.

Q. 동적 콘텐츠도 처리할 수 있나요?

A. Nginx는 정적 파일에 최적화되어 있습니다. 동적 콘텐츠는 백엔드 서버로 프록시하세요.

Q. 무료인가요?

A. 네, Nginx는 오픈소스이며 무료입니다. Nginx Plus는 유료 엔터프라이즈 버전입니다.

Q. 프로덕션에서 사용해도 되나요?

A. 네, Netflix, Airbnb, NASA 등 많은 기업에서 사용합니다.

Q. Nginx가 이벤트 기반이라는 말은 무슨 뜻인가요?

A. 워커가 요청마다 스레드를 붙이기보다, epoll/kqueue로 “준비된 소켓”만 처리하는 이벤트 루프 모델을 쓴다는 뜻입니다. 동시 접속이 많을 때 컨텍스트 스위칭과 메모리 부담을 줄이는 데 유리합니다.

Q. reload 전에 nginx -t를 쓰는 이유는?

A. 설정 문법 오류를 적용 전에 검사합니다. 잘못된 설정을 올리면 워커 기동 실패나 예상치 못한 기본값 적용으로 장애로 이어질 수 있으므로, 테스트 후 배포하는 습관이 중요합니다.