infra
Platform

모듈 맵

[Infra Ops] Nginx 리버스 프록시와 로드밸런싱 설정

0 / 52 완료

펼치기
0 / 52 완료0%

Infra-ops · 13 / 52

[Infra Ops] Nginx 리버스 프록시와 로드밸런싱 설정

proxy_pass, upstream 블록, 로드밸런싱 알고리즘, WebSocket 프록시까지 — 트래픽을 여러 백엔드로 분산하는 Nginx 설정을 실무 관점에서 익힙니다

🚨INCIDENT ALERT
HIGH

배포한 지 3개월 된 서비스에 사용자가 몰리기 시작했습니다. 단일 WAS 서버 하나로는 동시 접속 500명을 버티지 못하고, CPU가 100%를 찍고 응답이 멈춥니다. 인프라팀 슬랙에 "백엔드 서버 2대 더 추가할게요, Nginx에서 분산해 주세요"라는 메시지가 날아옵니다.

Nginx 설정 파일 하나로 이 요청을 해결할 수 있습니다. 그 핵심이 리버스 프록시와 upstream 블록입니다.

이번 챕터에서 배울 것
  • 1포워드 프록시와 리버스 프록시의 차이를 구조적으로 설명할 수 있다
  • 2proxy_pass로 단일 백엔드에 트래픽을 전달하고 헤더를 올바르게 설정할 수 있다
  • 3upstream 블록으로 서버 그룹을 정의하고 로드밸런싱 알고리즘을 선택할 수 있다
  • 4WebSocket 연결을 프록시로 처리하는 설정을 작성할 수 있다
  • 5헬스체크와 backup 서버로 장애 대응 구조를 구성할 수 있다
실습 환경 준비
Nginx 설치 확인
nginx -v
Nginx 서비스 상태 확인
systemctl status nginx --no-pager
설정 디렉터리 확인
ls /etc/nginx/conf.d/ /etc/nginx/sites-available/ 2>/dev/null
테스트용 백엔드 서버 실행 (Python HTTP)
python3 -m http.server 8081 --directory /tmp &

리버스 프록시 개념

💡개념

포워드 프록시 vs 리버스 프록시

"프록시"라는 단어가 같아서 혼동하기 쉽지만, 포워드 프록시와 리버스 프록시는 트래픽이 흐르는 방향과 보호하는 대상이 완전히 다릅니다. 실무에서 "Nginx를 프록시로 써라"는 말을 들었을 때, 클라이언트를 대신하는 역할인지 서버를 보호하는 역할인지 구분하지 못하면 설정 방향 자체가 틀려집니다. 리버스 프록시의 핵심은 백엔드 서버를 인터넷에 직접 노출하지 않고, 트래픽 제어를 한 지점에서 처리한다는 것입니다.

Nginx 리버스 프록시 구조 — 클라이언트는 Nginx만 보고, 백엔드 서버는 보호된다

왜 "리버스"라는 단어가 붙었을까요? 방향이 반대이기 때문입니다. 포워드 프록시는 클라이언트 앞에 서서 내부 사용자가 외부 인터넷에 나가는 트래픽을 대신 처리합니다 (사내 방화벽, VPN이 대표적). 리버스 프록시는 서버 앞에 서서 외부 클라이언트가 내부 서버로 들어오는 트래픽을 받습니다.

클라이언트 입장에서는 Nginx 뒤에 백엔드 서버가 몇 대 있는지, 어떤 언어로 짜여 있는지 알 수 없습니다. 이 불투명성이 보안, 부하분산, 캐싱, SSL 종단 처리를 한 곳에서 해결하는 구조를 만듭니다.

구분포워드 프록시리버스 프록시
위치클라이언트 앞서버 앞
대신 처리클라이언트의 외부 요청서버로 들어오는 요청
대표 사용처사내망, VPN, 캐시Nginx, HAProxy, AWS ALB
보호 대상클라이언트 신원백엔드 서버 구조
Nginx
# 리버스 프록시 가장 기본 형태
server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;  # 백엔드 서버로 전달
    }
}
💡개념

proxy_pass와 proxy_set_header — 헤더 전달의 중요성

proxy_pass만 설정하면 트래픽은 넘어가지만 문제가 생깁니다. 백엔드 서버는 요청을 보낸 주체가 클라이언트가 아니라 Nginx(127.0.0.1)라고 인식합니다. 이렇게 되면 접근 로그 분석, IP 기반 차단, 지역 기반 기능이 모두 망가집니다.

proxy_set_header는 이 문제를 해결합니다. Nginx가 실제 클라이언트 정보를 헤더에 담아 백엔드로 전달합니다. 실무에서 이 설정을 빠뜨리면 "왜 모든 접속이 내부 IP로만 찍히죠?"라는 질문이 나옵니다.

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

    location / {
        proxy_pass http://127.0.0.1:8080;

        # 실제 클라이언트 IP를 백엔드에 전달
        proxy_set_header X-Real-IP $remote_addr;

        # 프록시 체인 전체 IP 목록 (여러 프록시를 거쳤을 때 유용)
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 원본 Host 헤더 전달 (백엔드가 가상호스트 기반일 때 필수)
        proxy_set_header Host $host;

        # 백엔드가 HTTP/1.1을 사용하도록 강제 (기본값은 HTTP/1.0)
        proxy_http_version 1.1;

        # 프록시 연결 타임아웃
        proxy_connect_timeout 10s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

자주 쓰는 Nginx 변수:

변수의미
$remote_addr직접 연결한 IP (프록시 앞단이면 프록시 IP)
$proxy_add_x_forwarded_for기존 X-Forwarded-For + $remote_addr
$host요청의 Host 헤더 값
$request_uri전체 요청 URI (쿼리스트링 포함)
$schemehttp 또는 https

Nginx upstream 로드밸런싱 — 알고리즘 비교

💡개념

upstream 블록과 로드밸런싱 알고리즘

WAS 서버를 3대로 늘렸는데 특정 서버에 요청이 몰려 CPU가 튀는 현상이 있었습니다. 파일 업로드 API라 요청 처리 시간이 일정하지 않았고, 라운드로빈으로는 "처리 중인 요청이 많은 서버"를 고려하지 못했기 때문입니다. 알고리즘을 least_conn으로 바꾸니 부하가 균등해졌습니다. upstream 블록 없이 여러 서버를 관리하면 설정이 복잡해지고 가중치, 헬스체크, 알고리즘 설정도 불가합니다.

Nginx 오픈소스 버전이 지원하는 세 가지 알고리즘은 각각 적합한 상황이 다릅니다.

Nginx
# round-robin (기본값) — 순서대로 균등 분배
upstream app_backend {
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
    server 10.0.0.3:8080;
}

# weight — 서버 성능이 다를 때
upstream app_backend_weighted {
    server 10.0.0.1:8080 weight=3;  # 3배 더 많은 요청
    server 10.0.0.2:8080 weight=1;
}

# least_conn — 활성 연결이 적은 서버 우선
upstream app_backend_lc {
    least_conn;
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

# ip_hash — 같은 클라이언트는 같은 서버로 (세션 고정)
upstream app_backend_session {
    ip_hash;
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

알고리즘 선택 가이드:

알고리즘적합한 상황주의사항
round-robinAPI, 정적 콘텐츠 — 요청이 거의 동일한 무게기본값, 별도 설정 불필요
weight서버 사양이 다를 때성능 차이 측정 후 비율 설정
least_conn파일 업로드, 스트리밍 — 요청마다 시간이 다름짧은 요청엔 round-robin이 나을 수도
ip_hash서버 측 세션 저장, 외부 세션스토어 미사용NAT 환경에서 특정 서버 집중 발생 가능

실습: 기본 리버스 프록시 설정

1단일 백엔드 리버스 프록시 설정

아래 내용을 파일에 작성합니다. backup 디렉티브를 붙인 8082는 8081이 다운됐을 때만 사용됩니다. 저장 후 nginx -t로 문법 검사하고 reload합니다.

Nginx
upstream app_servers {
    server 127.0.0.1:8081;
    server 127.0.0.1:8082 backup;
}

server {
    listen 9090;
    server_name _;

    access_log /var/log/nginx/proxy-demo-access.log;
    error_log  /var/log/nginx/proxy-demo-error.log;

    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;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_http_version  1.1;
        proxy_connect_timeout 10s;
        proxy_read_timeout    60s;
    }
}
로컬 터미널
sudo nginx -t && sudo systemctl reload nginx
sudo nano /etc/nginx/conf.d/proxy-demo.conf
🔍실행 후 확인할 것
  • nginx -t 출력에 'syntax is ok'와 'test is successful' 두 줄이 나오는지 확인
  • curl -s http://localhost:9090/ -I 로 200 또는 502 응답이 오는지 확인 (백엔드 서버가 없으면 502 정상)
  • sudo nginx -T | grep upstream 으로 upstream 블록이 로드됐는지 확인
  • tail -f /var/log/nginx/proxy-demo-access.log 로 요청이 기록되는지 확인
2로드밸런싱 설정 — least_conn 적용

아래 내용을 작성합니다. max_fails=3 fail_timeout=30s는 수동 헬스체크 역할을 합니다. keepalive 32는 Nginx와 백엔드 사이 유휴 커넥션을 유지해 오버헤드를 줄입니다.

Nginx
upstream api_pool {
    least_conn;

    server 10.0.0.1:8080 weight=2 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:8080 weight=2 max_fails=3 fail_timeout=30s;
    server 10.0.0.3:8080 weight=1 max_fails=3 fail_timeout=30s;

    keepalive 32;
}

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

    location /api/ {
        proxy_pass http://api_pool;

        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_http_version 1.1;
        proxy_set_header Connection "";
    }
}
로컬 터미널
sudo nginx -t
sudo nano /etc/nginx/conf.d/lb-demo.conf
🔍실행 후 확인할 것
  • nginx -t 결과 확인 — 실제 백엔드 서버가 없어도 설정 문법 검사는 통과해야 함
  • sudo nginx -T 2>/dev/null | grep -A 10 'upstream api_pool' 로 upstream 설정 전체 확인
  • curl -H 'Host: api.example.com' http://localhost/api/ 로 요청 전달 테스트
  • 여러 번 요청 후 /var/log/nginx/access.log 에서 upstream_addr 확인 (least_conn 분산 여부)

WebSocket 프록시 설정

💡개념

WebSocket — 프로토콜 업그레이드 처리

채팅 기능을 배포했는데 연결이 5초마다 끊겼습니다. Nginx에 WebSocket 설정이 없어서 연결 유지가 안 됐기 때문입니다. proxy_http_version 1.1과 Upgrade 헤더 설정을 추가하니 연결이 지속됐습니다. WebSocket은 처음에 HTTP로 시작해 프로토콜 전환을 요청하는 구조라, Nginx가 이 헤더를 전달하지 않으면 백엔드가 전환을 거부합니다.

WebSocket은 일반 HTTP와 달리 연결을 끊지 않고 양방향으로 지속합니다. 채팅, 알림, 실시간 대시보드가 대표적입니다. 문제는 Nginx의 기본 프록시 설정이 WebSocket을 인식하지 못한다는 점입니다.

WebSocket 연결은 처음에 HTTP로 시작해 Upgrade: websocket 헤더로 프로토콜 전환을 요청합니다. Nginx가 이 헤더를 그냥 지나치면 백엔드는 프로토콜 전환을 거부하고 연결이 실패합니다. proxy_http_version 1.1Connection Upgrade 헤더 설정이 이 문제를 해결합니다.

Nginx
upstream ws_backend {
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
}

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

    # WebSocket 엔드포인트
    location /ws/ {
        proxy_pass http://ws_backend;

        # WebSocket 필수 설정
        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;  # WebSocket Upgrade 헤더 전달
        proxy_set_header Connection "upgrade";       # 커넥션 유지

        proxy_set_header Host            $host;
        proxy_set_header X-Real-IP       $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # WebSocket은 연결이 오래 유지되므로 타임아웃 늘리기
        proxy_read_timeout 3600s;  # 1시간
        proxy_send_timeout 3600s;
    }

    # 일반 HTTP API는 별도 location
    location /api/ {
        proxy_pass http://ws_backend;
        proxy_set_header Host            $host;
        proxy_set_header X-Real-IP       $remote_addr;
        proxy_http_version 1.1;
    }
}

트러블슈팅

원인: Nginx가 upstream 서버에 연결하지 못했습니다. 가장 흔한 원인 두 가지입니다.

  1. 백엔드 서버가 실행 중이지 않거나 포트가 다르다
  2. 방화벽이 Nginx → 백엔드 연결을 차단한다
로컬 터미널
# 1. 백엔드 서버가 해당 포트에서 실제로 listening하는지 확인
ss -tlnp | grep ':8080'

# 2. Nginx에서 백엔드 포트로 직접 연결 테스트
curl -v http://127.0.0.1:8080/health

# 3. Nginx 에러 로그 확인 — 실제 에러 메시지 확인
sudo tail -50 /var/log/nginx/error.log | grep upstream

# 4. SELinux가 Nginx의 네트워크 연결을 차단하는지 확인 (CentOS/RHEL)
sudo ausearch -m avc -ts recent | grep nginx
# 허용이 필요하면:
sudo setsebool -P httpd_can_network_connect 1

# 5. upstream 설정 내 IP/포트 오타 여부 확인
sudo nginx -T | grep -A 5 'upstream'

보통 curl http://127.0.0.1:8080이 성공하면 Nginx 설정 문제이고, 실패하면 백엔드 서버 문제입니다.

원인: proxy_set_header X-Real-IPX-Forwarded-For 설정이 누락됐거나, 백엔드 애플리케이션이 해당 헤더를 읽지 않고 있습니다.

로컬 터미널
# 1. Nginx 설정에서 헤더 설정 확인
sudo nginx -T | grep -E 'X-Real-IP|X-Forwarded'

# 설정이 없다면 location 블록에 추가
# proxy_set_header X-Real-IP         $remote_addr;
# proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;

# 2. 실제로 헤더가 전달되는지 테스트용 엔드포인트로 확인
# Python 백엔드라면 request.headers.get('X-Real-IP') 출력

# 3. 중간에 다른 프록시가 더 있다면 real_ip_header 모듈 필요
# nginx.conf http 블록 또는 server 블록에 추가:
# set_real_ip_from 10.0.0.0/8;          # 앞단 로드밸런서 IP 대역
# real_ip_header X-Forwarded-For;
# real_ip_recursive on;

# 4. 설정 변경 후 반드시 reload
sudo nginx -t && sudo systemctl reload nginx

AWS ALB, CloudFlare 등 앞단 프록시가 있을 경우 X-Forwarded-For에 IP가 여러 개 쌓입니다. real_ip_module로 신뢰할 프록시 대역을 설정하면 $remote_addr에서 진짜 클라이언트 IP를 꺼낼 수 있습니다.

실무 맥락

💼
실무 맥락
현업 패턴

실제 업무에서 이 설정이 쓰이는 세 가지 상황:

1. 배포 무중단 처리 (Rolling Deploy)

upstream에서 특정 서버를 일시적으로 제외하고 배포한 뒤 다시 추가하는 패턴입니다.

로컬 터미널
# 배포 전: 10.0.0.2 서버를 down 처리
# upstream 블록에 'server 10.0.0.2:8080 down;' 추가 후 reload

# 배포 완료 후: down 제거 후 reload
sudo nginx -t && sudo systemctl reload nginx

Nginx Plus를 쓰면 API로 동적 처리가 가능하지만, 오픈소스는 설정 수정 + reload가 표준 방법입니다.

2. 경로별 다른 서비스로 라우팅 (마이크로서비스)

Nginx
server {
    listen 80;
    server_name api.company.com;

    location /auth/    { proxy_pass http://auth_service; }
    location /orders/  { proxy_pass http://order_service; }
    location /catalog/ { proxy_pass http://catalog_service; }
}

API 게이트웨이 없이 Nginx 설정만으로 마이크로서비스를 하나의 도메인에서 서비스하는 구조입니다. 실제 현장에서 빠르게 쓰는 방법입니다.

3. 헬스체크와 알람 연동

max_failsfail_timeout으로 자동 격리가 되지만, 실제로는 모니터링(Prometheus, Datadog)과 연동해 upstream 서버가 제외됐을 때 알람이 오도록 구성합니다. Nginx의 ngx_http_upstream_module 메트릭을 expose하는 nginx-prometheus-exporter를 함께 띄우는 것이 일반적입니다.

다음 모듈에서는 Nginx SSL/TLS 설정과 Let's Encrypt 인증서 자동 갱신을 다룹니다.

지식 확인

퀴즈 — 4문제

Q1

upstream 블록에서 least_conn을 쓸 때 유리한 상황은?

Q2

proxy_set_header X-Real-IP $remote_addr 설정이 없으면 어떤 문제가 생기나?

Q3

ip_hash 알고리즘의 특징과 주의사항으로 옳은 것은?

Q4

WebSocket 프록시에서 proxy_http_version 1.1과 Upgrade 헤더 설정이 필요한 이유는?

0 / 4 답변

🧪 실습으로 확인하기

Nginx 설치 및 기동

초급

Linux 서버에 Nginx를 설치하고 systemd 서비스로 등록하여 80포트에서 응답하는 상태까지 만든다.

30📋 3단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

infra-ops중급 · 60
[Infra Ops] httpd VirtualHost 설정과 리버스 프록시 운영
인프라 서비스 운영 트랙 계속
linux입문 · 30
[Linux] 개발자가 왜 리눅스 서버와 커맨드라인을 반드시 배워야 하는가
Linux 트랙 시작점