Reverse Proxy (리버스 프록시)
TL;DR
- Reverse Proxy (리버스 프록시)의 핵심 개념을 빠르게 파악할 수 있다.
- 등장 배경과 채택 이유를 통해 왜 필요한지 맥락을 이해할 수 있다.
- 주요 특징과 상세 내용을 통해 실무 적용 포인트를 확인할 수 있다.
1. 개념
Reverse Proxy (리버스 프록시)의 핵심 정의와 문제 공간을 간단히 정리한다.
2. 배경
이 주제가 등장한 기술적·조직적 배경과 기존 접근의 한계를 설명한다.
3. 이유
왜 지금 이 방식을 채택해야 하는지, 기대 효과와 트레이드오프를 함께 정리한다.
4. 특징
핵심 동작 방식, 장단점, 적용 시 주의점을 빠르게 훑을 수 있도록 요약한다.
5. 상세 내용
Reverse Proxy (리버스 프록시)
작성일: 2026-03-03 카테고리: Infra / Network / Web Server 포함 내용: Reverse Proxy, Forward Proxy, Load Balancing, SSL Termination, Nginx, HAProxy, Envoy, Traefik, Caddy, CDN, API Gateway, Service Mesh, Zero Trust, Kubernetes Ingress
목차
- 리버스 프록시란?
- 등장 배경 - 왜 만들어졌나?
- 왜 필요한가?
- 주요 기능들
- 주요 구현체 비교
- 산업 표준으로서의 위치
- 아키텍처 패턴
- 설정 예제
- Production 운영 가이드
- 현대적 활용
- 관련 기술 비교
- 요약
- 프록시 헤더 심층 분석 (X-Forwarded-For, X-Real-IP, X-Forwarded-Proto)
1. 리버스 프록시란?
1.1 정의
리버스 프록시(Reverse Proxy)는 클라이언트와 백엔드 서버 사이에 위치하여, 클라이언트의 요청을 대신 받아 적절한 백엔드 서버로 전달하고 그 응답을 다시 클라이언트에게 반환하는 중간 서버이다.
클라이언트는 리버스 프록시의 존재를 인식하지 못하며, 마치 리버스 프록시 자체가 최종 서버인 것처럼 동작한다.
1.2 기본 구조
┌──────────────────────────────────────────────────────────────────┐
│ 리버스 프록시 기본 구조 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Client │─────>│ Internet │─────>│ Reverse Proxy │ │
│ │ (사용자) │ │ (인터넷) │ │ (리버스 프록시) │ │
│ └────────┘ └──────────┘ └───────┬───────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ │ │ │ │
│ v v v │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Backend │ │ Backend │ │ Backend │ │
│ │ Server A │ │ Server B │ │ Server C │ │
│ │ (App #1) │ │ (App #2) │ │ (App #3) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
1.3 요청 흐름 (7단계)
[요청 흐름 - 7단계 상세]
1단계: 클라이언트가 https://api.dalmung.kr 로 요청을 보낸다
┌────────┐
│ Client │── GET /api/users ──>
└────────┘
2단계: DNS가 리버스 프록시의 IP 주소를 반환한다
DNS: api.dalmung.kr → 203.0.113.10 (리버스 프록시 IP)
3단계: 요청이 리버스 프록시에 도착한다
┌───────────────┐
│ Reverse Proxy │ <── 요청 수신
│ 203.0.113.10 │
└───────────────┘
4단계: 리버스 프록시가 라우팅 규칙에 따라 백엔드를 선택한다
규칙: /api/* → backend-pool (Round Robin)
선택: Backend Server B (10.0.1.2:8080)
5단계: 리버스 프록시가 백엔드 서버로 요청을 전달한다
┌───────────────┐ ┌──────────┐
│ Reverse Proxy │──────> │ Backend │
│ │ │ Server B │
└───────────────┘ └──────────┘
6단계: 백엔드 서버가 응답을 리버스 프록시로 반환한다
┌───────────────┐ <───── ┌──────────┐
│ Reverse Proxy │ │ Backend │
│ │ 200 │ Server B │
└───────────────┘ OK └──────────┘
7단계: 리버스 프록시가 응답을 클라이언트에게 반환한다
┌────────┐ <── 200 OK ── ┌───────────────┐
│ Client │ │ Reverse Proxy │
└────────┘ (캐싱, 압축) └───────────────┘
1.4 핵심 개념
| 개념 | 설명 |
|---|---|
| 투명성(Transparency) | 클라이언트는 리버스 프록시 뒤의 실제 서버 구조를 알지 못한다 |
| 단일 진입점(Single Entry Point) | 모든 외부 트래픽이 하나의 지점을 통과한다 |
| 트래픽 중재(Traffic Mediation) | 요청/응답을 검사, 수정, 라우팅할 수 있다 |
1.5 Forward Proxy vs Reverse Proxy
┌─────────────────────────────────────────────────────────────────────┐
│ Forward Proxy (포워드 프록시) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌───────────────┐ ┌──────────┐ ┌────────────┐ │
│ │ Client │───>│ Forward Proxy │───>│ Internet │───>│ Server │ │
│ │ (사용자) │ │ (포워드 프록시) │ │ (인터넷) │ │ (목적지 서버) │ │
│ └────────┘ └───────────────┘ └──────────┘ └────────────┘ │
│ │
│ * 클라이언트 측에 위치 │
│ * 서버는 실제 클라이언트가 누구인지 모른다 │
│ * 사용 예: 사내 프록시, VPN, 익명화 │
│ │
├─────────────────────────────────────────────────────────────────────┤
│ Reverse Proxy (리버스 프록시) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌──────────┐ ┌───────────────┐ ┌────────────┐ │
│ │ Client │───>│ Internet │───>│ Reverse Proxy │───>│ Backend │ │
│ │ (사용자) │ │ (인터넷) │ │ (리버스 프록시) │ │ Server │ │
│ └────────┘ └──────────┘ └───────────────┘ └────────────┘ │
│ │
│ * 서버 측에 위치 │
│ * 클라이언트는 실제 백엔드 서버가 무엇인지 모른다 │
│ * 사용 예: 로드 밸런싱, SSL 종료, 캐싱 │
│ │
└─────────────────────────────────────────────────────────────────────┘
| 비교 항목 | Forward Proxy | Reverse Proxy |
|---|---|---|
| 위치 | 클라이언트 측 | 서버 측 |
| 보호 대상 | 클라이언트를 보호 | 서버를 보호 |
| 숨기는 것 | 클라이언트의 신원 | 서버의 신원과 구조 |
| 주요 목적 | 접근 제어, 캐싱, 익명화 | 로드 밸런싱, 보안, 가속 |
| 클라이언트 인식 | 클라이언트가 프록시 존재를 안다 | 클라이언트가 프록시 존재를 모른다 |
| 설정 주체 | 클라이언트 측 관리자 | 서버 측 관리자 |
| 대표 예시 | Squid, 사내 프록시 | Nginx, HAProxy, Envoy |
2. 등장 배경 - 왜 만들어졌나?
2.1 역사적 타임라인
┌──────────────────────────────────────────────────────────────────────────┐
│ 리버스 프록시의 역사 │
├──────────┬───────────────────────────────────────────────────────────────┤
│ 시대 │ 주요 사건 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 1970s │ ARPANET 탄생 - 네트워크 초기, 프록시 개념 없음 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 1990 │ CERN HTTPd - 최초의 웹 서버, Tim Berners-Lee가 개발 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 1995 │ Apache HTTP Server 등장 - mod_proxy로 리버스 프록시 기능 제공 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 1999 │ HAProxy 프로젝트 시작 - Willy Tarreau가 개발 │
│ │ 전용 로드 밸런서/리버스 프록시의 시작 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 2000 │ HAProxy 1.0 공개 - L4/L7 로드 밸런싱 전문 소프트웨어 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 2004 │ Nginx 공개 - Igor Sysoev가 C10K 문제 해결을 위해 개발 │
│ │ Event-driven 아키텍처로 고성능 달성 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 2009 │ Node.js 등장 - 마이크로서비스 확산의 기반이 됨 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 2013 │ Docker 등장 - 컨테이너화로 서비스 분리 가속화 │
│ │ 리버스 프록시의 중요성 급증 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 2015 │ Kubernetes 1.0 출시 - Ingress Controller 개념 등장 │
│ │ Traefik 공개 - 자동 서비스 디스커버리 지원 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 2016 │ Envoy Proxy 오픈소스화 (Lyft) - 클라우드 네이티브 프록시 │
│ │ xDS(Discovery Service) 프로토콜 도입 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 2017 │ Istio 서비스 메시 등장 - Envoy를 사이드카로 활용 │
│ │ Caddy 2.0 - 자동 HTTPS 지원 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 2018 │ Envoy가 CNCF Graduated 프로젝트로 승격 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 2020-22 │ Gateway API 개발 - Kubernetes Ingress의 차세대 표준 │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 2023 │ Istio Ambient Mesh - 사이드카 없는 서비스 메시 │
│ │ Gateway API v1.0 GA │
├──────────┼───────────────────────────────────────────────────────────────┤
│ 2024-26 │ Gateway API 생태계 확대, Ambient Mesh 프로덕션 채택 확산 │
│ │ eBPF 기반 프록시 기술 (Cilium) 성숙 │
└──────────┴───────────────────────────────────────────────────────────────┘
2.2 리버스 프록시가 해결한 3가지 핵심 문제
문제 1: 단일 서버의 한계
[문제 상황]
┌──────────────────┐
사용자 1000명 ───>│ 단일 웹 서버 │──> 과부하, 응답 지연, 장애
│ CPU 100%, OOM │
└──────────────────┘
[해결]
┌───────────────┐ ┌──────────┐
사용자 1000명 ───>│ Reverse Proxy │────>│ Server 1 │ 333명 처리
│ (로드 밸런싱) │────>│ Server 2 │ 333명 처리
│ │────>│ Server 3 │ 334명 처리
└───────────────┘ └──────────┘
문제 2: 보안 노출
[문제 상황]
공격자 ──> 직접 접근 ──> 백엔드 서버 (내부 구조 노출, 직접 공격 가능)
[해결]
공격자 ──> Reverse Proxy ──> 차단/필터링 ──> 안전한 백엔드 서버
(WAF, Rate Limit, (내부 IP 은닉)
IP 차단)
문제 3: SSL 처리 부담
[문제 상황]
각 서버마다 SSL 인증서 설치, 갱신, 암복호화 CPU 부담
Client ──HTTPS──> Server 1 (SSL 처리 + 비즈니스 로직)
Client ──HTTPS──> Server 2 (SSL 처리 + 비즈니스 로직)
Client ──HTTPS──> Server 3 (SSL 처리 + 비즈니스 로직)
[해결]
Client ──HTTPS──> Reverse Proxy ──HTTP──> Server 1 (비즈니스 로직만)
(SSL 인증서 1곳, Server 2 (비즈니스 로직만)
중앙 관리) Server 3 (비즈니스 로직만)
2.3 진화 과정
CERN HTTPd (1990)
│ 최초의 웹 서버, 프록시 개념 부재
v
Apache HTTP Server + mod_proxy (1995)
│ 범용 웹 서버에 리버스 프록시 기능 추가
│ Process-per-connection 모델 (동시 접속 한계)
v
Nginx (2004)
│ Event-driven 아키텍처로 C10K 문제 해결
│ 리버스 프록시 전용 설계
v
HAProxy (2000~)
│ 로드 밸런싱 전문, L4/L7 지원
│ 고성능 TCP/HTTP 프록시
v
Envoy (2016)
│ 클라우드 네이티브, xDS 동적 설정
│ 관찰 가능성(Observability) 내장
v
현대 (2024-2026)
Gateway API, Ambient Mesh, eBPF 프록시
서비스 메시와 통합, 자동화된 인프라
3. 왜 필요한가?
3.1 리버스 프록시가 제공하는 핵심 가치
| 가치 | 설명 | 효과 |
|---|---|---|
| 로드 밸런싱 | 트래픽을 여러 서버에 분산 | 확장성, 가용성 향상 |
| SSL 터미네이션 | SSL/TLS 처리를 중앙화 | 백엔드 서버 부담 감소 |
| 캐싱 | 자주 요청되는 콘텐츠를 캐시 | 응답 속도 향상, 서버 부하 감소 |
| 보안 | WAF, Rate Limiting, IP 차단 | 공격 방어, 내부 구조 은닉 |
| 압축 | 응답 데이터 압축 전송 | 대역폭 절약, 전송 속도 향상 |
| 로깅/모니터링 | 중앙 집중 로그 수집 | 트래픽 분석, 장애 추적 |
3.2 리버스 프록시 없는 구조 vs 있는 구조
┌──────────────────────────────────────────────────────────────────┐
│ 리버스 프록시 없는 구조 (비권장) │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌──────────┐ 각 서버가 직접 노출 │
│ │ │────>│ Server 1 │ - SSL 개별 관리 │
│ │ │ │ :443 │ - 보안 개별 적용 │
│ │ Client │ └──────────┘ - 로그 분산 │
│ │ │ ┌──────────┐ - 장애 시 직접 영향 │
│ │ │────>│ Server 2 │ - IP 직접 노출 │
│ │ │ │ :443 │ - 스케일링 어려움 │
│ └────────┘ └──────────┘ │
│ │
│ 문제점: │
│ - DNS에 서버 IP 직접 등록 (A 레코드 다수) │
│ - 서버 추가/제거 시 DNS 변경 필요 (전파 지연 24-48시간) │
│ - DDoS 공격에 각 서버가 개별 대응 │
│ - SSL 인증서 서버마다 설치/갱신 │
│ │
├──────────────────────────────────────────────────────────────────┤
│ 리버스 프록시 있는 구조 (권장) │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌───────────────┐ ┌──────────┐ │
│ │ │ │ │──>│ Server 1 │ 내부 네트워크 │
│ │ │ │ Reverse Proxy │ │ :8080 │ - SSL 중앙 관리 │
│ │ Client │──>│ (단일 진입점) │ └──────────┘ - 보안 통합 적용 │
│ │ │ │ :443 │ ┌──────────┐ - 로그 중앙화 │
│ │ │ │ │──>│ Server 2 │ - 무중단 스케일링 │
│ └────────┘ └───────────────┘ │ :8080 │ - IP 은닉 │
│ └──────────┘ │
│ │
│ 장점: │
│ - DNS에 프록시 IP 1개만 등록 │
│ - 서버 추가/제거가 프록시 설정만으로 가능 (즉시 반영) │
│ - DDoS 방어를 프록시에서 일괄 처리 │
│ - SSL 인증서 1곳에서 관리 │
│ │
└──────────────────────────────────────────────────────────────────┘
3.3 Production 환경에서의 필수성
주요 보안 표준 및 가이드라인에서 리버스 프록시 사용을 권장 또는 요구한다:
| 표준/가이드 | 요구 사항 |
|---|---|
| OWASP | 웹 애플리케이션 앞단에 WAF/리버스 프록시 배치 권장 |
| NIST SP 800-44 | 웹 서버 보안 가이드에서 리버스 프록시를 통한 보안 계층화 권장 |
| PCI DSS | 카드 결제 데이터 처리 시 WAF 또는 동등한 보호 수단 요구 |
결론: 트래픽이 의미 있는 수준(일일 수만 요청 이상)인 모든 프로덕션 환경에서 리버스 프록시는 선택이 아닌 필수이다.
4. 주요 기능들
4.1 로드 밸런싱
트래픽을 여러 백엔드 서버에 분산하여 처리량을 높이고 가용성을 확보한다.
로드 밸런싱 알고리즘 비교
┌──────────────────┬────────────────────────────────────────────────────┐
│ 알고리즘 │ 설명 │
├──────────────────┼────────────────────────────────────────────────────┤
│ Round Robin │ 순서대로 돌아가며 요청 배분 │
│ │ 가장 단순, 서버 사양이 동일할 때 적합 │
├──────────────────┼────────────────────────────────────────────────────┤
│ Weighted RR │ 서버별 가중치를 부여하여 비율에 따라 분배 │
│ │ 서버 사양이 다를 때 유용 (고사양 서버에 더 많은 트래픽) │
├──────────────────┼────────────────────────────────────────────────────┤
│ Least │ 현재 활성 연결이 가장 적은 서버로 전달 │
│ Connections │ 요청 처리 시간이 불균등할 때 효과적 │
├──────────────────┼────────────────────────────────────────────────────┤
│ IP Hash │ 클라이언트 IP 해시 기반으로 서버 결정 │
│ │ 동일 클라이언트는 항상 같은 서버 (세션 고정) │
├──────────────────┼────────────────────────────────────────────────────┤
│ Consistent Hash │ 해시 링을 사용하여 서버 추가/제거 시 영향 최소화 │
│ │ 캐시 서버 앞단에서 Cache Hit율 극대화 │
├──────────────────┼────────────────────────────────────────────────────┤
│ Least Time │ 응답 시간 + 활성 연결 수를 종합하여 가장 빠른 서버 선택 │
│ │ Nginx Plus, HAProxy에서 지원 │
└──────────────────┴────────────────────────────────────────────────────┘
Nginx 로드 밸런싱 설정 예시
# ── Round Robin (기본값) ──
upstream dalmung_backend {
server 10.0.1.1:8080; # 백엔드 서버 1
server 10.0.1.2:8080; # 백엔드 서버 2
server 10.0.1.3:8080; # 백엔드 서버 3
}
# ── Weighted Round Robin ──
upstream dalmung_weighted {
server 10.0.1.1:8080 weight=5; # 고사양 서버: 요청의 50%
server 10.0.1.2:8080 weight=3; # 중간 사양: 요청의 30%
server 10.0.1.3:8080 weight=2; # 저사양 서버: 요청의 20%
}
# ── Least Connections ──
upstream dalmung_least {
least_conn; # 최소 연결 알고리즘 활성화
server 10.0.1.1:8080;
server 10.0.1.2:8080;
server 10.0.1.3:8080;
}
# ── IP Hash (세션 고정) ──
upstream dalmung_iphash {
ip_hash; # 클라이언트 IP 기반 해시
server 10.0.1.1:8080;
server 10.0.1.2:8080;
server 10.0.1.3:8080;
}
# ── Consistent Hash (Nginx Plus 또는 3rd party 모듈) ──
upstream dalmung_consistent {
hash $request_uri consistent; # URI 기반 일관된 해시
server 10.0.1.1:8080;
server 10.0.1.2:8080;
server 10.0.1.3:8080;
}
4.2 SSL/TLS 터미네이션
세 가지 SSL 처리 모드
모드 1: SSL Termination (SSL 종료)
┌────────┐ HTTPS ┌───────────────┐ HTTP ┌──────────┐
│ Client │────────>│ Reverse Proxy │────────>│ Backend │
│ │ (암호화) │ (SSL 복호화) │ (평문) │ Server │
└────────┘ └───────────────┘ └──────────┘
* 가장 일반적인 방식
* 리버스 프록시에서 SSL을 복호화하고 백엔드에는 HTTP로 전달
* 백엔드 서버의 CPU 부담 감소
* 내부 네트워크가 안전하다는 전제 필요
모드 2: SSL Passthrough (SSL 통과)
┌────────┐ HTTPS ┌───────────────┐ HTTPS ┌──────────┐
│ Client │────────>│ Reverse Proxy │────────>│ Backend │
│ │ (암호화) │ (복호화 안 함) │ (암호화) │ Server │
└────────┘ └───────────────┘ └──────────┘
* 리버스 프록시가 SSL을 복호화하지 않고 그대로 전달
* End-to-End 암호화 유지
* L4(TCP) 수준에서만 프록시 동작
* URL 기반 라우팅 불가 (암호화 상태이므로)
모드 3: SSL Re-encryption (재암호화 / mTLS)
┌────────┐ HTTPS ┌───────────────┐ HTTPS ┌──────────┐
│ Client │────────>│ Reverse Proxy │────────>│ Backend │
│ │ (인증서A) │ (복호화 + 재암호화)│ (인증서B) │ Server │
└────────┘ └───────────────┘ └──────────┘
* 리버스 프록시에서 복호화 후 다시 암호화하여 백엔드로 전달
* 외부-내부 모두 암호화 (Zero Trust 환경에서 사용)
* CPU 부담이 가장 크지만 보안 수준이 가장 높음
* 금융, 의료 등 규제 산업에서 요구
4.3 캐싱과 콘텐츠 가속
리버스 프록시가 백엔드 응답을 캐시하여 동일 요청에 대해 즉시 응답한다.
캐시 가능한 콘텐츠 유형
| 유형 | 예시 | 캐시 효과 |
|---|---|---|
| 정적 파일 | CSS, JS, 이미지, 폰트 | 매우 높음 - 백엔드 요청 제거 |
| API 응답 | GET /api/products (변경 빈도 낮음) | 높음 - TTL 기반 캐시 |
| HTML 페이지 | 서버 사이드 렌더링 결과 | 중간 - 사용자별 콘텐츠 주의 |
| 동적 콘텐츠 | 실시간 데이터, 사용자별 정보 | 낮음 - 캐시 부적합 |
Spoon-Feeding 기법
[Spoon-Feeding: 느린 클라이언트 대응 기법]
일반적인 문제:
┌────────┐ 느린 연결 ┌──────────┐
│ 느린 │<──────────│ Backend │ 백엔드가 느린 클라이언트 응답을
│ Client │ (100KB/s) │ Server │ 기다리며 연결을 유지 = 리소스 낭비
└────────┘ └──────────┘
Spoon-Feeding 해결:
┌────────┐ 느린 연결 ┌───────────────┐ 빠른 연결 ┌──────────┐
│ 느린 │<──────────│ Reverse Proxy │<──────────│ Backend │
│ Client │ (100KB/s) │ (버퍼에 저장) │ (즉시 반환) │ Server │
└────────┘ └───────────────┘ └──────────┘
* 리버스 프록시가 백엔드 응답을 버퍼에 빠르게 저장
* 백엔드 서버 연결을 즉시 해제 (다른 요청 처리 가능)
* 느린 클라이언트에게는 프록시가 천천히 전달
* 백엔드 서버의 동시 연결 수를 획기적으로 줄임
4.4 압축
응답 데이터를 압축하여 전송 대역폭을 절약한다.
┌──────────────┬────────────┬────────────┬────────────────────────────┐
│ 압축 알고리즘 │ 압축률 │ 속도 │ 특징 │
├──────────────┼────────────┼────────────┼────────────────────────────┤
│ Gzip │ 보통 │ 빠름 │ 가장 범용적, 모든 브라우저 지원 │
│ │ (약 70-80%)│ │ 레벨 1-9 (보통 4-6 사용) │
├──────────────┼────────────┼────────────┼────────────────────────────┤
│ Brotli │ 높음 │ 보통 │ Google 개발, HTTPS에서만 동작 │
│ │ (약 80-90%)│ │ Gzip 대비 15-25% 더 작음 │
├──────────────┼────────────┼────────────┼────────────────────────────┤
│ Zstandard │ 높음 │ 매우 빠름 │ Facebook 개발, 실시간 압축 적합│
│ (zstd) │ (약 80-85%)│ │ 압축/해제 속도가 빠름 │
└──────────────┴────────────┴────────────┴────────────────────────────┘
4.5 보안
리버스 프록시는 다양한 보안 기능을 통해 백엔드 서버를 보호한다.
DDoS 방어
# Nginx에서의 Rate Limiting 설정
# 초당 10개 요청으로 제한, 버스트 20개 허용
limit_req_zone $binary_remote_addr zone=dalmung_limit:10m rate=10r/s;
server {
location /api/ {
limit_req zone=dalmung_limit burst=20 nodelay;
# nodelay: 버스트 내 요청은 즉시 처리 (지연 없음)
proxy_pass http://backend;
}
}
WAF (Web Application Firewall)
- SQL Injection, XSS, CSRF 등 공격 패턴 탐지 및 차단
- ModSecurity (Nginx/Apache 모듈)가 대표적
- OWASP Core Rule Set(CRS) 적용
IP 필터링
# 특정 IP 대역만 허용하는 관리자 페이지
location /admin/ {
allow 10.0.0.0/8; # 사내 네트워크 허용
allow 192.168.1.0/24; # VPN 대역 허용
deny all; # 나머지 모두 차단
proxy_pass http://admin_backend;
}
4.6 요청 라우팅과 URL 재작성
server {
listen 443 ssl;
server_name api.dalmung.kr;
# 경로 기반 라우팅: URL 패턴에 따라 다른 백엔드로 전달
location /api/v1/users {
proxy_pass http://user_service; # 사용자 서비스
}
location /api/v1/orders {
proxy_pass http://order_service; # 주문 서비스
}
location /api/v1/products {
proxy_pass http://product_service; # 상품 서비스
}
# URL 재작성: 외부 URL을 내부 구조로 변환
location /legacy/member {
rewrite ^/legacy/member(.*)$ /api/v1/users$1 break;
proxy_pass http://user_service;
}
# 정적 파일은 프록시 자체에서 처리
location /static/ {
root /var/www/dalmung;
expires 30d; # 30일 캐시
add_header Cache-Control "public, immutable";
}
}
4.7 헬스 체킹
백엔드 서버의 상태를 지속적으로 확인하여 장애 서버에 트래픽을 보내지 않는다.
┌──────────────────┬─────────────────────┬────────────────────────┐
│ 유형 │ Passive (수동) │ Active (능동) │
├──────────────────┼─────────────────────┼────────────────────────┤
│ 작동 방식 │ 실제 요청의 응답을 │ 주기적으로 헬스 체크 │
│ │ 모니터링하여 판단 │ 전용 요청을 보내어 확인 │
├──────────────────┼─────────────────────┼────────────────────────┤
│ 감지 속도 │ 느림 (오류 발생 후) │ 빠름 (주기적 확인) │
├──────────────────┼─────────────────────┼────────────────────────┤
│ 네트워크 부하 │ 없음 │ 약간의 추가 트래픽 │
├──────────────────┼─────────────────────┼────────────────────────┤
│ 설정 복잡도 │ 낮음 │ 중간 │
├──────────────────┼─────────────────────┼────────────────────────┤
│ Nginx OSS 지원 │ 지원 (max_fails) │ 미지원 (Plus 전용) │
├──────────────────┼─────────────────────┼────────────────────────┤
│ HAProxy 지원 │ 지원 │ 지원 (option httpchk) │
└──────────────────┴─────────────────────┴────────────────────────┘
# Nginx - Passive 헬스 체크 설정
upstream dalmung_backend {
server 10.0.1.1:8080 max_fails=3 fail_timeout=30s;
# max_fails=3: 3번 실패하면 해당 서버를 비정상으로 표시
# fail_timeout=30s: 30초 후에 다시 시도
server 10.0.1.2:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.3:8080 backup;
# backup: 위의 서버들이 모두 다운되었을 때만 사용
}
4.8 커넥션 풀링
[커넥션 풀링: 백엔드 연결 재사용]
풀링 없이:
Client 요청 1 ──> Proxy ──[새 TCP 연결]──> Backend 연결 수립 비용 매번 발생
Client 요청 2 ──> Proxy ──[새 TCP 연결]──> Backend 3-way handshake 반복
Client 요청 3 ──> Proxy ──[새 TCP 연결]──> Backend 서버 포트 고갈 위험
풀링 사용:
Client 요청 1 ──> Proxy ──[기존 연결 재사용]──> Backend 즉시 전달
Client 요청 2 ──> Proxy ──[기존 연결 재사용]──> Backend 연결 수립 비용 제거
Client 요청 3 ──> Proxy ──[기존 연결 재사용]──> Backend 성능 대폭 향상
upstream dalmung_backend {
server 10.0.1.1:8080;
server 10.0.1.2:8080;
keepalive 64; # 최대 64개의 유휴 연결을 풀에 유지
keepalive_time 1h; # 연결 최대 수명 1시간
keepalive_timeout 60s; # 유휴 연결 타임아웃 60초
keepalive_requests 1000; # 연결당 최대 요청 수 1000
}
4.9 WebSocket 프록시
# WebSocket 업그레이드를 지원하는 리버스 프록시 설정
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name ws.dalmung.kr;
location /ws/ {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
# WebSocket 업그레이드 헤더 전달
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# WebSocket은 장시간 연결이므로 타임아웃 연장
proxy_read_timeout 3600s; # 1시간
proxy_send_timeout 3600s;
}
}
4.10 헤더 조작
server {
location / {
# ── 백엔드로 전달하는 헤더 설정 ──
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_set_header X-Request-ID $request_id;
# ── 클라이언트 응답에서 제거하는 헤더 ──
proxy_hide_header X-Powered-By; # 서버 기술 스택 은닉
proxy_hide_header Server; # 서버 소프트웨어 정보 은닉
# ── 보안 헤더 추가 ──
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 Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
proxy_pass http://backend;
}
}
5. 주요 구현체 비교
5.1 Nginx
┌──────────────────────────────────────────────────────────────────┐
│ Nginx │
├──────────────────────────────────────────────────────────────────┤
│ 개발자: Igor Sysoev (2004) │
│ 언어: C │
│ 아키텍처: Event-driven, 비동기 논블로킹 I/O │
│ 시장 점유율: 33-42% (W3Techs 기준) │
│ 라이선스: BSD 2-Clause (OSS) / 상용 (Nginx Plus) │
└──────────────────────────────────────────────────────────────────┘
Worker Process 모델
┌─────────────────────────────────────────────────────┐
│ Nginx Process 구조 │
├─────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ │
│ │ Master Process │ 설정 읽기, 포트 바인딩, │
│ │ (root 권한) │ Worker 생성/관리 │
│ └────────┬─────────┘ │
│ │ │
│ ┌─────┼──────┬──────────┐ │
│ v v v v │
│ ┌──────┐┌──────┐┌──────┐┌──────┐ │
│ │Worker││Worker││Worker││Worker│ CPU 코어당 1개 │
│ │ #1 ││ #2 ││ #3 ││ #4 │ Event-driven │
│ └──────┘└──────┘└──────┘└──────┘ 수천 연결 동시 처리 │
│ │
│ 각 Worker는 단일 스레드로 수천 개의 연결을 처리 │
│ epoll(Linux) / kqueue(BSD)를 사용하여 │
│ 비동기 이벤트 루프 방식으로 동작 │
│ │
└─────────────────────────────────────────────────────┘
Nginx의 강점:
- 매우 낮은 메모리 사용량 (연결당 약 2.5KB)
- 정적 파일 서빙 성능이 탁월
- 설정 문법이 직관적이고 문서가 풍부
- 모듈 시스템으로 기능 확장 가능
- 가장 넓은 생태계와 커뮤니티
Nginx의 한계:
- 동적 설정 변경 시 reload 필요 (Nginx Plus 제외)
- Active 헬스 체크는 Nginx Plus 전용
- L4 로드 밸런싱은 stream 모듈로 별도 설정
5.2 HAProxy
┌──────────────────────────────────────────────────────────────────┐
│ HAProxy │
├──────────────────────────────────────────────────────────────────┤
│ 개발자: Willy Tarreau (2000) │
│ 언어: C │
│ 아키텍처: Event-driven, 멀티스레드 지원 │
│ 특화: L4/L7 로드 밸런싱 전문 │
│ 라이선스: GPL v2 (OSS) / 상용 (HAProxy Enterprise) │
└──────────────────────────────────────────────────────────────────┘
HAProxy의 강점:
- 로드 밸런싱 성능이 최고 수준 (벤치마크 약 42K RPS)
- L4(TCP)와 L7(HTTP) 모두 네이티브 지원
- 매우 정밀한 ACL 기반 라우팅
- Stick Table을 통한 실시간 트래픽 통계 및 Rate Limiting
- 무중단 설정 변경(Hitless Reload) 지원
- 내장 통계 대시보드
Stick Table 개념:
┌──────────────────────────────────────────────────────┐
│ Stick Table: HAProxy의 실시간 인메모리 키-값 저장소 │
├──────────────────────────────────────────────────────┤
│ │
│ 키: 클라이언트 IP │
│ 값: 요청 수, 바이트 수, 오류 수, 연결 수 등 │
│ │
│ ┌─────────────┬─────────┬──────────┬──────────┐ │
│ │ Client IP │ req_cnt │ conn_cur │ gpc0 │ │
│ ├─────────────┼─────────┼──────────┼──────────┤ │
│ │ 192.168.1.1 │ 150 │ 3 │ 0 │ │
│ │ 10.0.0.55 │ 5200 │ 50 │ 1 (차단) │ │
│ │ 172.16.0.3 │ 30 │ 1 │ 0 │ │
│ └─────────────┴─────────┴──────────┴──────────┘ │
│ │
│ 활용: Rate Limiting, DDoS 방어, 세션 고정 │
│ HAProxy 노드 간 Stick Table 복제도 가능 │
│ │
└──────────────────────────────────────────────────────┘
5.3 Apache HTTP Server
┌──────────────────────────────────────────────────────────────────┐
│ Apache HTTP Server │
├──────────────────────────────────────────────────────────────────┤
│ 개발: Apache Software Foundation (1995) │
│ 언어: C │
│ 모듈: mod_proxy, mod_proxy_http, mod_proxy_balancer │
│ 시장 점유율: 24-35% (감소 추세) │
│ 라이선스: Apache License 2.0 │
└──────────────────────────────────────────────────────────────────┘
MPM (Multi-Processing Module) 비교:
┌──────────────┬──────────────────────────────────────────────┐
│ MPM │ 특징 │
├──────────────┼──────────────────────────────────────────────┤
│ Prefork │ 요청당 프로세스 1개 생성 │
│ │ 안정적이나 메모리 사용량 높음 │
│ │ PHP mod_php와 호환성 좋음 │
├──────────────┼──────────────────────────────────────────────┤
│ Worker │ 멀티스레드 방식, 프로세스당 여러 스레드 │
│ │ Prefork 대비 메모리 효율적 │
│ │ 스레드 안전하지 않은 모듈 사용 불가 │
├──────────────┼──────────────────────────────────────────────┤
│ Event │ Worker 기반 + 비동기 Keep-Alive 처리 │
│ │ Apache 2.4부터 기본 MPM │
│ │ Nginx와 유사한 Event-driven 요소 도입 │
└──────────────┴──────────────────────────────────────────────┘
Apache는 범용 웹 서버로서의 오랜 역사와 .htaccess를 통한 디렉토리별 설정, 풍부한 모듈 생태계가 강점이나, 리버스 프록시 전용으로는 Nginx나 HAProxy에 비해 성능과 효율성에서 뒤처진다.
5.4 Envoy
┌──────────────────────────────────────────────────────────────────┐
│ Envoy │
├──────────────────────────────────────────────────────────────────┤
│ 개발: Lyft → CNCF (2016 오픈소스화) │
│ 언어: C++ │
│ 아키텍처: L7 프록시, 필터 체인 기반 │
│ 특화: 클라우드 네이티브, 서비스 메시 데이터 플레인 │
│ 라이선스: Apache License 2.0 │
│ CNCF: Graduated 프로젝트 (2018) │
└──────────────────────────────────────────────────────────────────┘
xDS (Discovery Service) 프로토콜:
┌────────────────────────────────────────────────────────────┐
│ xDS: Envoy의 동적 설정 프로토콜 │
├────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ gRPC/REST ┌──────────────────┐ │
│ │ Envoy │<────────────>│ Control Plane │ │
│ │ (Data Plane) │ xDS API │ (Istio 등) │ │
│ └──────────────┘ └──────────────────┘ │
│ │
│ xDS 세부 프로토콜: │
│ ┌─────┬─────────────────────────────────────────┐ │
│ │ LDS │ Listener Discovery - 리스너 설정 │ │
│ │ RDS │ Route Discovery - 라우팅 규칙 │ │
│ │ CDS │ Cluster Discovery - 백엔드 클러스터 정보 │ │
│ │ EDS │ Endpoint Discovery - 서버 엔드포인트 목록 │ │
│ │ SDS │ Secret Discovery - TLS 인증서/키 │ │
│ └─────┴─────────────────────────────────────────┘ │
│ │
│ * 설정 변경 시 재시작 불필요 (동적 업데이트) │
│ * Control Plane이 설정을 푸시하면 Envoy가 즉시 반영 │
│ │
└────────────────────────────────────────────────────────────┘
필터 체인 아키텍처:
요청 흐름:
┌─────────┐ ┌───────────┐ ┌──────────┐ ┌────────────┐ ┌─────────┐
│ Network │─>│ HTTP Conn │─>│ Router │─>│ Rate Limit │─>│ Backend │
│ Filter │ │ Manager │ │ Filter │ │ Filter │ │ Cluster │
└─────────┘ └───────────┘ └──────────┘ └────────────┘ └─────────┘
각 필터는 독립적으로 요청/응답을 검사, 수정, 차단할 수 있다.
커스텀 필터를 C++ 또는 Wasm으로 개발 가능.
5.5 Traefik
┌──────────────────────────────────────────────────────────────────┐
│ Traefik │
├──────────────────────────────────────────────────────────────────┤
│ 개발: Traefik Labs (2015) │
│ 언어: Go │
│ 특화: 자동 서비스 디스커버리, 컨테이너 네이티브 │
│ 라이선스: MIT (OSS) / 상용 (Traefik Enterprise) │
└──────────────────────────────────────────────────────────────────┘
자동 디스커버리:
┌─────────────────────────────────────────────────────────────┐
│ Traefik의 자동 디스커버리 아키텍처 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ Docker │ │ Kubernetes │ │ Consul/etcd │ │
│ │ Engine │ │ API Server │ │ (KV Store) │ │
│ └────┬─────┘ └─────┬──────┘ └──────┬───────┘ │
│ │ │ │ │
│ v v v │
│ ┌─────────────────────────────────────────────┐ │
│ │ Traefik (Provider) │ │
│ │ Docker 라벨, K8s Ingress, Consul 설정을 │ │
│ │ 자동으로 감지하여 라우팅 규칙 생성 │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ Docker 라벨 예시: │
│ labels: │
│ - "traefik.http.routers.app.rule=Host(`dalmung.kr`)" │
│ - "traefik.http.services.app.loadbalancer.port=8080" │
│ │
└─────────────────────────────────────────────────────────────┘
Let’s Encrypt 자동 인증서:
- 인증서 발급, 갱신, 적용이 모두 자동
- ACME 프로토콜 네이티브 지원
- DNS Challenge, HTTP Challenge 모두 지원
5.6 Caddy
┌──────────────────────────────────────────────────────────────────┐
│ Caddy │
├──────────────────────────────────────────────────────────────────┤
│ 개발: Matt Holt (2015) │
│ 언어: Go │
│ 특화: 자동 HTTPS, 극도로 간단한 설정 │
│ 라이선스: Apache License 2.0 │
└──────────────────────────────────────────────────────────────────┘
Caddyfile 간결함:
# Caddy - 리버스 프록시 설정 (이것이 전부)
dalmung.kr {
reverse_proxy localhost:8080
}
# 자동으로 다음이 적용된다:
# - Let's Encrypt HTTPS 인증서 발급
# - HTTP -> HTTPS 리다이렉트
# - TLS 1.3 우선 적용
# - OCSP Stapling
# - HTTP/2 활성화
Caddy의 강점:
- 설정 없이 자동 HTTPS (Let’s Encrypt + ZeroSSL)
- Caddyfile 문법이 극도로 간결
- API를 통한 동적 설정 변경 지원
- 단일 바이너리 배포
Caddy의 한계:
- 대규모 트래픽에서 Nginx/HAProxy 대비 성능이 낮음
- 기업 환경에서의 검증 사례가 상대적으로 적음
- Go 런타임 오버헤드
5.7 클라우드 관리형 서비스
| 서비스 | 제공사 | 계층 | 특징 |
|---|---|---|---|
| ALB (Application LB) | AWS | L7 | HTTP/HTTPS, 경로/호스트 라우팅, WAF 통합 |
| NLB (Network LB) | AWS | L4 | 초저지연, 고정 IP, TCP/UDP |
| Cloud Load Balancing | GCP | L4/L7 | 글로벌 단일 Anycast IP, 자동 스케일링 |
| Application Gateway | Azure | L7 | WAF v2, URL 기반 라우팅, SSL 오프로드 |
| Front Door | Azure | L7 | 글로벌 CDN + L7 LB, WAF 통합 |
클라우드 관리형의 장점:
- 인프라 관리 불필요 (패치, 스케일링 자동)
- 글로벌 분산 배치
- 다른 클라우드 서비스와 긴밀한 통합
- SLA 보장 (99.99% 이상)
클라우드 관리형의 단점:
- 비용이 상대적으로 높음
- 세밀한 설정 커스터마이징 제한
- 벤더 종속(Vendor Lock-in)
- 디버깅이 어려울 수 있음
5.8 성능 비교 (벤치마크)
┌──────────────────────────────────────────────────────────────────────────┐
│ HTTP RPS (Requests Per Second) 벤치마크 │
│ (단일 노드, keep-alive, 소규모 JSON 응답) │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ HAProxy ████████████████████████████████████████████ ~42,000 │
│ │
│ Traefik ███████████████████ ~19,000 │
│ │
│ Envoy ██████████████████ ~18,500 │
│ │
│ Nginx Plus ███████████████ ~15,200 │
│ │
│ Nginx OSS ███████████ ~11,700 │
│ │
├──────────────────────────────────────────────────────────────────────────┤
│ * 벤치마크는 환경에 따라 크게 달라질 수 있음 │
│ * Nginx는 정적 파일 서빙에서는 다른 프록시를 크게 앞섬 │
│ * HAProxy는 순수 프록시/LB 성능에 최적화되어 있음 │
│ * 실제 운영에서는 기능, 운영 편의성, 생태계도 중요 │
└──────────────────────────────────────────────────────────────────────────┘
5.9 종합 비교표
┌──────────┬────────────┬──────────┬───────────┬──────────┬──────────┐
│ 항목 │ Nginx │ HAProxy │ Envoy │ Traefik │ Caddy │
├──────────┼────────────┼──────────┼───────────┼──────────┼──────────┤
│ 언어 │ C │ C │ C++ │ Go │ Go │
├──────────┼────────────┼──────────┼───────────┼──────────┼──────────┤
│ L4 지원 │ stream │ 네이티브 │ 네이티브 │ 제한적 │ 제한적 │
├──────────┼────────────┼──────────┼───────────┼──────────┼──────────┤
│ L7 지원 │ 네이티브 │ 네이티브 │ 네이티브 │ 네이티브 │ 네이티브 │
├──────────┼────────────┼──────────┼───────────┼──────────┼──────────┤
│ 동적 설정 │ reload │ hitless │ xDS API │ 자동 │ API │
├──────────┼────────────┼──────────┼───────────┼──────────┼──────────┤
│ 자동 HTTPS│ 수동 │ 수동 │ SDS │ 자동 │ 자동 │
├──────────┼────────────┼──────────┼───────────┼──────────┼──────────┤
│ 서비스 │ 없음 │ 없음 │ xDS │ Docker, │ 없음 │
│ 디스커버리 │ │ │ │ K8s 등 │ │
├──────────┼────────────┼──────────┼───────────┼──────────┼──────────┤
│ 관찰가능성 │ 기본 로그 │ 통계 │ 풍부 │ 메트릭 │ 기본 │
│ │ (확장 필요) │ 대시보드 │ (Prometheus│ Prom., │ 로그 │
│ │ │ │ Jaeger등) │ Jaeger │ │
├──────────┼────────────┼──────────┼───────────┼──────────┼──────────┤
│ 최적 용도 │ 범용 웹 서버│ 순수 LB │ 서비스 메시│ 컨테이너 │ 소규모 │
│ │ + 프록시 │ / 프록시 │ 사이드카 │ 오케스트 │ 프로젝트 │
│ │ │ │ │ 레이션 │ │
└──────────┴────────────┴──────────┴───────────┴──────────┴──────────┘
6. 산업 표준으로서의 위치
6.1 시장 점유율 (W3Techs 기준, 2026)
┌──────────────────────────────────────────────────────────────────┐
│ 웹 서버 / 리버스 프록시 시장 점유율 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ Cloudflare █████████████████████ 21.6% │
│ (CDN/Proxy) │
│ │
│ Nginx ██████████████████████████████████████ 33-42% │
│ │
│ Apache ████████████████████████████ 24-35% │
│ │
│ LiteSpeed █████████ ~12% │
│ │
│ 기타 ██████ 나머지 │
│ (Caddy, HAProxy, Traefik, Envoy 등) │
│ │
├──────────────────────────────────────────────────────────────────┤
│ * Cloudflare는 CDN이면서 리버스 프록시 역할을 수행 │
│ * Nginx는 리버스 프록시 단독 사용과 웹 서버 겸용 모두 포함 │
│ * Apache는 레거시 시스템에서의 점유율이 높음 │
│ * Envoy, Traefik은 Kubernetes 환경에서 급성장 중 │
└──────────────────────────────────────────────────────────────────┘
6.2 Production 필수 여부
결론: YES - 규모 있는 모든 프로덕션 환경에서 필수이다.
근거:
- 대형 서비스의 99% 이상이 리버스 프록시를 사용
- 보안 표준(OWASP, PCI DSS)이 WAF/프록시 배치를 요구
- 컨테이너/Kubernetes 환경에서는 Ingress Controller가 사실상 필수
- CDN(Cloudflare, CloudFront) 자체가 글로벌 리버스 프록시
- 클라우드 환경에서 Load Balancer 없이 서비스 노출은 비현실적
7. 아키텍처 패턴
7.1 단일 리버스 프록시
┌─────────────────────────────────────────────────────┐
│ 단일 리버스 프록시 패턴 │
├─────────────────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌───────────────┐ ┌──────────┐ │
│ │ Client │──>│ Reverse Proxy │──>│ Backend │ │
│ │ │ │ (Nginx 1대) │ │ Servers │ │
│ └────────┘ └───────────────┘ └──────────┘ │
│ │
│ 장점: 구성이 간단, 관리 비용 낮음 │
│ 단점: SPOF (Single Point of Failure) │
│ 적합: 개발 환경, 소규모 서비스, 트래픽이 낮은 경우 │
│ │
└─────────────────────────────────────────────────────┘
7.2 멀티 계층 프록시
┌──────────────────────────────────────────────────────────────────┐
│ 멀티 계층 프록시 패턴 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌──────┐ ┌────────────┐ ┌──────────┐ │
│ │ Client │──>│ CDN │──>│ Edge Proxy │──>│ Internal │ │
│ │ │ │(전세계)│ │ (L7 라우팅) │ │ LB │ │
│ └────────┘ └──────┘ └────────────┘ └────┬─────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ v v v │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ │ App Pod │ │ App Pod │ │ App Pod │
│ │ #1 │ │ #2 │ │ #3 │
│ └─────────┘ └─────────┘ └─────────┘
│ │
│ 각 계층의 역할: │
│ CDN : 정적 콘텐츠 캐시, DDoS 방어, 글로벌 가속 │
│ Edge Proxy: SSL 종료, WAF, 경로 기반 라우팅 │
│ Internal LB: 백엔드 서버 간 로드 밸런싱, 헬스 체크 │
│ │
└──────────────────────────────────────────────────────────────────┘
7.3 사이드카 패턴 (Service Mesh)
┌──────────────────────────────────────────────────────────────────┐
│ 사이드카 프록시 패턴 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────┐ ┌────────────────────────────┐ │
│ │ Pod A │ │ Pod B │ │
│ │ ┌─────────┐ ┌─────────┐ │ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │ App │ │ Envoy │ │ │ │ Envoy │ │ App │ │ │
│ │ │Container│→│Sidecar │─┼──┼─>│Sidecar │→│Container│ │ │
│ │ │ │ │ Proxy │ │ │ │ Proxy │ │ │ │ │
│ │ └─────────┘ └─────────┘ │ │ └─────────┘ └─────────┘ │ │
│ └────────────────────────────┘ └────────────────────────────┘ │
│ │
│ * 모든 서비스 트래픽이 사이드카 프록시를 통과 │
│ * mTLS 자동 적용 (서비스 간 암호화) │
│ * 트래픽 관찰가능성 (메트릭, 트레이싱, 로깅) 자동 수집 │
│ * 서킷 브레이커, 재시도, 타임아웃 정책 중앙 관리 │
│ * 대표 구현: Istio + Envoy │
│ │
└──────────────────────────────────────────────────────────────────┘
7.4 API Gateway 패턴
┌──────────────────────────────────────────────────────────────────┐
│ API Gateway 패턴 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ Web │ │Mobile│ │ IoT │ 다양한 클라이언트 │
│ │ App │ │ App │ │Device│ │
│ └──┬───┘ └──┬───┘ └──┬───┘ │
│ │ │ │ │
│ v v v │
│ ┌──────────────────────────────────────┐ │
│ │ API Gateway │ │
│ │ ┌───────────────────────────────┐ │ │
│ │ │ 인증/인가 Rate Limiting │ │ │
│ │ │ 요청 변환 응답 캐싱 │ │ │
│ │ │ API 버전 모니터링/로깅 │ │ │
│ │ └───────────────────────────────┘ │ │
│ └─────────────┬────────────────────────┘ │
│ │ │
│ ┌──────────┼──────────┐ │
│ v v v │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ User │ │Order │ │ Pay │ 마이크로서비스 │
│ │ Svc │ │ Svc │ │ Svc │ │
│ └──────┘ └──────┘ └──────┘ │
│ │
│ API Gateway = Reverse Proxy + API 관리 기능 │
│ 대표 구현: Kong, AWS API Gateway, Apigee │
│ │
└──────────────────────────────────────────────────────────────────┘
7.5 CDN as 글로벌 리버스 프록시
┌──────────────────────────────────────────────────────────────────┐
│ CDN = 글로벌 분산 리버스 프록시 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 인터넷 │ │
│ │ │ │
│ │ 한국 사용자 ──> [서울 PoP] │ │
│ │ │ │ │
│ │ 미국 사용자 ──> [버지니아 PoP] 각 PoP가 캐시 보유 │ │
│ │ │ Cache HIT = 즉시 응답 │ │
│ │ 유럽 사용자 ──> [프랑크푸르트 PoP] Cache MISS = Origin│ │
│ │ │ │ │
│ └─────────────────────┼──────────────────────────────┘ │
│ │ │
│ v │
│ ┌─────────────────┐ │
│ │ Origin Server │ │
│ │ (원본 서버) │ │
│ └─────────────────┘ │
│ │
│ CDN의 리버스 프록시 기능: │
│ - 전 세계 PoP(Point of Presence)에서 콘텐츠 캐싱 │
│ - DDoS 방어 (대규모 네트워크 용량 활용) │
│ - SSL 종료 (Edge에서 처리) │
│ - 이미지 최적화, 압축 │
│ - 대표: Cloudflare, AWS CloudFront, Akamai │
│ │
└──────────────────────────────────────────────────────────────────┘
8. 설정 예제
8.1 Nginx 기본 리버스 프록시
# /etc/nginx/conf.d/dalmung-basic.conf
# 달멍 프로젝트 - 기본 리버스 프록시 설정
# 백엔드 서버 그룹 정의
upstream dalmung_app {
server 10.0.1.1:8080; # 애플리케이션 서버 1
server 10.0.1.2:8080; # 애플리케이션 서버 2
}
server {
listen 80;
server_name dalmung.kr www.dalmung.kr;
# HTTP -> HTTPS 리다이렉트
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name dalmung.kr www.dalmung.kr;
# SSL 인증서 설정
ssl_certificate /etc/ssl/certs/dalmung.kr.crt;
ssl_certificate_key /etc/ssl/private/dalmung.kr.key;
# 프록시 기본 설정
location / {
proxy_pass http://dalmung_app;
# 원본 요청 정보를 백엔드에 전달
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;
}
}
8.2 Nginx SSL + 로드 밸런싱 (Production 설정)
# /etc/nginx/nginx.conf
# 달멍 프로젝트 - Production 리버스 프록시 전체 설정
user nginx;
worker_processes auto; # CPU 코어 수에 맞게 자동 설정
worker_rlimit_nofile 65535; # 파일 디스크립터 제한 확대
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 4096; # Worker당 최대 연결 수
multi_accept on; # 한 번에 여러 연결 수락
use epoll; # Linux에서 최적의 이벤트 방식
}
http {
# -- 기본 설정 --
include /etc/nginx/mime.types;
default_type application/octet-stream;
charset utf-8;
# -- 성능 최적화 --
sendfile on; # 커널 레벨 파일 전송
tcp_nopush on; # 헤더와 데이터를 함께 전송
tcp_nodelay on; # 작은 패킷도 즉시 전송
keepalive_timeout 65; # Keep-Alive 타임아웃
keepalive_requests 1000; # 연결당 최대 요청 수
# -- Gzip 압축 --
gzip on;
gzip_vary on;
gzip_proxied any; # 프록시된 요청도 압축
gzip_comp_level 4; # 압축 레벨 (1-9, 4가 균형점)
gzip_min_length 256; # 256바이트 이상만 압축
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml;
# -- 보안 헤더 --
server_tokens off; # Nginx 버전 정보 숨김
# -- Rate Limiting 영역 정의 --
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/s;
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
# -- JSON 로그 포맷 (모니터링 도구 연동) --
log_format json_combined escape=json
'{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"request_method":"$request_method",'
'"request_uri":"$request_uri",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_addr":"$upstream_addr",'
'"http_user_agent":"$http_user_agent",'
'"http_referer":"$http_referer",'
'"request_id":"$request_id"'
'}';
access_log /var/log/nginx/access.log json_combined;
# -- 백엔드 서버 그룹 --
upstream dalmung_api {
least_conn; # 최소 연결 알고리즘
server 10.0.1.1:8080 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.1.2:8080 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.1.3:8080 weight=2 max_fails=3 fail_timeout=30s;
server 10.0.1.4:8080 backup; # 장애 시 백업 서버
keepalive 64; # 백엔드 커넥션 풀
keepalive_time 1h;
keepalive_timeout 60s;
}
upstream dalmung_websocket {
ip_hash; # WebSocket은 세션 고정 필요
server 10.0.2.1:8081;
server 10.0.2.2:8081;
}
# -- WebSocket 업그레이드 맵 --
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# -- SSL 공통 설정 --
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;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling (인증서 유효성 확인 가속)
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
# -- HTTP -> HTTPS 리다이렉트 --
server {
listen 80 default_server;
listen [::]:80 default_server;
return 301 https://$host$request_uri;
}
# -- 메인 서버 블록 --
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name dalmung.kr www.dalmung.kr;
ssl_certificate /etc/ssl/certs/dalmung.kr.fullchain.pem;
ssl_certificate_key /etc/ssl/private/dalmung.kr.key.pem;
# HSTS (HTTP Strict Transport Security)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# -- API 프록시 --
location /api/ {
limit_req zone=api_limit burst=50 nodelay;
proxy_pass http://dalmung_api;
proxy_http_version 1.1;
proxy_set_header Connection ""; # keepalive 활성화
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_set_header X-Request-ID $request_id;
# 타임아웃 설정
proxy_connect_timeout 5s; # 연결 타임아웃
proxy_read_timeout 30s; # 응답 읽기 타임아웃
proxy_send_timeout 10s; # 요청 전송 타임아웃
# 버퍼 설정
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 16k;
# 오류 시 다음 서버로 재시도
proxy_next_upstream error timeout http_502 http_503;
proxy_next_upstream_tries 2;
}
# -- 로그인 엔드포인트 (엄격한 Rate Limiting) --
location /api/auth/login {
limit_req zone=login_limit burst=3 nodelay;
proxy_pass http://dalmung_api;
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 --
location /ws/ {
proxy_pass http://dalmung_websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
# -- 정적 파일 --
location /static/ {
root /var/www/dalmung;
expires 30d;
add_header Cache-Control "public, immutable";
access_log off; # 정적 파일 로그 비활성화
}
# -- 헬스 체크 엔드포인트 --
location /health {
access_log off;
return 200 "OK";
add_header Content-Type text/plain;
}
}
}
8.3 HAProxy L7 라우팅
# /etc/haproxy/haproxy.cfg
# 별빛상점 프로젝트 - HAProxy L7 라우팅 설정
global
log stdout format raw local0 # 표준 출력으로 로그
maxconn 50000 # 최대 동시 연결 수
nbthread 4 # 워커 스레드 수
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
tune.ssl.default-dh-param 2048
defaults
log global
mode http # L7 (HTTP) 모드
option httplog # 상세 HTTP 로그
option dontlognull # 헬스 체크 로그 제외
option forwardfor # X-Forwarded-For 헤더 자동 추가
option http-server-close # 서버 측 연결 재사용
timeout connect 5s # 백엔드 연결 타임아웃
timeout client 30s # 클라이언트 타임아웃
timeout server 30s # 서버 응답 타임아웃
timeout http-request 10s # HTTP 요청 수신 타임아웃
timeout http-keep-alive 10s # Keep-Alive 타임아웃
retries 3 # 재시도 횟수
default-server init-addr last,libc,none
# -- 통계 대시보드 --
listen stats
bind *:8404 # 통계 페이지 포트
stats enable
stats uri /stats # http://proxy:8404/stats
stats refresh 10s # 10초마다 갱신
stats admin if LOCALHOST # 로컬에서만 관리 기능
stats auth admin:byeolbit2026 # 인증 정보
# -- 프론트엔드 (클라이언트 접속 지점) --
frontend byeolbit_front
bind *:80
bind *:443 ssl crt /etc/ssl/certs/byeolbit.pem alpn h2,http/1.1
# HTTP -> HTTPS 리다이렉트
http-request redirect scheme https unless { ssl_fc }
# 보안 헤더 추가
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"
http-response set-header X-Frame-Options "SAMEORIGIN"
http-response set-header X-Content-Type-Options "nosniff"
# -- ACL 기반 라우팅 규칙 --
acl is_api path_beg /api/ # /api/ 로 시작하는 경로
acl is_admin path_beg /admin/ # /admin/ 으로 시작
acl is_ws hdr(Upgrade) -i websocket # WebSocket 업그레이드 요청
acl is_static path_end .css .js .png .jpg .svg .woff2 # 정적 파일
# Rate Limiting용 Stick Table
stick-table type ip size 100k expire 30s store http_req_rate(10s)
http-request track-sc0 src
# 10초 동안 100개 이상 요청한 IP를 차단
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 }
# ACL에 따라 백엔드 선택
use_backend byeolbit_api if is_api
use_backend byeolbit_admin if is_admin
use_backend byeolbit_ws if is_ws
use_backend byeolbit_static if is_static
default_backend byeolbit_web # 기본 백엔드
# -- 백엔드: API 서버 --
backend byeolbit_api
balance leastconn # 최소 연결 알고리즘
option httpchk GET /health # Active 헬스 체크
http-check expect status 200
server api1 10.0.1.1:8080 check inter 5s fall 3 rise 2
server api2 10.0.1.2:8080 check inter 5s fall 3 rise 2
server api3 10.0.1.3:8080 check inter 5s fall 3 rise 2 backup
# check: 헬스 체크 활성화
# inter 5s: 5초 간격으로 체크
# fall 3: 3번 실패 시 비정상 판정
# rise 2: 2번 성공 시 정상 복귀
# -- 백엔드: 관리자 페이지 --
backend byeolbit_admin
balance roundrobin
# 사내 IP만 접근 허용
acl is_internal src 10.0.0.0/8 192.168.0.0/16
http-request deny unless is_internal
server admin1 10.0.2.1:8090 check
# -- 백엔드: WebSocket --
backend byeolbit_ws
balance source # IP 기반 세션 고정
option http-server-close
timeout tunnel 3600s # WebSocket 장시간 연결
server ws1 10.0.3.1:8081 check
server ws2 10.0.3.2:8081 check
# -- 백엔드: 정적 파일 --
backend byeolbit_static
balance roundrobin
option httpchk HEAD /health
http-request set-header Cache-Control "public, max-age=2592000"
server static1 10.0.4.1:80 check
server static2 10.0.4.2:80 check
# -- 백엔드: 웹 애플리케이션 (기본) --
backend byeolbit_web
balance roundrobin
option httpchk GET /health
cookie SERVERID insert indirect nocache
server web1 10.0.5.1:3000 check cookie web1
server web2 10.0.5.2:3000 check cookie web2
9. Production 운영 가이드
9.1 성능 튜닝
Nginx 성능 튜닝 핵심 파라미터
# /etc/nginx/nginx.conf - 성능 튜닝 관련 설정
# ── 프로세스 설정 ──
worker_processes auto; # CPU 코어 수만큼 Worker 생성
worker_cpu_affinity auto; # 각 Worker를 특정 CPU 코어에 고정
worker_rlimit_nofile 65535; # Worker당 파일 디스크립터 제한
events {
worker_connections 4096; # Worker당 최대 동시 연결 수
# 총 최대 동시 연결 = worker_processes x worker_connections
# 예: 4 Worker x 4096 = 16,384 동시 연결
multi_accept on; # 한 번의 이벤트 루프에서 여러 연결 수락
use epoll; # Linux: epoll (가장 효율적)
}
http {
# ── 버퍼 설정 ──
client_body_buffer_size 16k; # 요청 본문 버퍼
client_header_buffer_size 1k; # 요청 헤더 버퍼
large_client_header_buffers 4 8k; # 큰 헤더용 추가 버퍼
client_max_body_size 10m; # 최대 요청 본문 크기
# ── 타임아웃 설정 ──
client_body_timeout 12; # 요청 본문 수신 타임아웃
client_header_timeout 12; # 요청 헤더 수신 타임아웃
send_timeout 10; # 응답 전송 타임아웃
keepalive_timeout 65; # Keep-Alive 연결 유지 시간
keepalive_requests 1000; # Keep-Alive당 최대 요청 수
# ── 파일 캐시 ──
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
}
운영체제 레벨 튜닝
# /etc/sysctl.conf - 커널 파라미터 튜닝
# 최대 파일 디스크립터 수 증가
fs.file-max = 65535
# TCP 연결 관련
net.core.somaxconn = 65535 # listen() 백로그 큐 크기
net.core.netdev_max_backlog = 5000 # 네트워크 장치 큐 크기
net.ipv4.tcp_max_syn_backlog = 65535 # SYN 백로그 큐
# TCP Keep-Alive
net.ipv4.tcp_keepalive_time = 600 # Keep-Alive 시작 시간 (초)
net.ipv4.tcp_keepalive_intvl = 60 # Keep-Alive 재전송 간격
net.ipv4.tcp_keepalive_probes = 3 # Keep-Alive 최대 재시도 횟수
# TIME_WAIT 소켓 재사용
net.ipv4.tcp_tw_reuse = 1 # TIME_WAIT 소켓 재사용 허용
net.ipv4.tcp_fin_timeout = 15 # FIN_WAIT2 타임아웃 단축
# 포트 범위 확대
net.ipv4.ip_local_port_range = 1024 65535
9.2 모니터링
JSON 액세스 로그 포맷
# 모니터링 도구(ELK, Loki, Datadog 등)와 연동하기 좋은 JSON 포맷
log_format json_monitoring escape=json
'{'
'"timestamp":"$time_iso8601",'
'"client_ip":"$remote_addr",'
'"method":"$request_method",'
'"uri":"$request_uri",'
'"status":$status,'
'"bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_time":"$upstream_response_time",'
'"upstream_addr":"$upstream_addr",'
'"upstream_status":"$upstream_status",'
'"user_agent":"$http_user_agent",'
'"request_id":"$request_id",'
'"ssl_protocol":"$ssl_protocol",'
'"ssl_cipher":"$ssl_cipher"'
'}';
핵심 모니터링 메트릭
┌────────────────────┬───────────────────────────────────────────────┐
│ 메트릭 │ 설명 및 임계값 │
├────────────────────┼───────────────────────────────────────────────┤
│ Request Rate │ 초당 요청 수 (RPS) │
│ (요청 처리율) │ 갑작스러운 급증 = DDoS 가능성 │
├────────────────────┼───────────────────────────────────────────────┤
│ Error Rate │ 4xx/5xx 응답 비율 │
│ (오류율) │ 5xx > 1% = 즉시 조사 필요 │
├────────────────────┼───────────────────────────────────────────────┤
│ Response Time │ 요청 처리 시간 (P50, P95, P99) │
│ (응답 시간) │ P99 > 1초 = 성능 저하 조사 │
├────────────────────┼───────────────────────────────────────────────┤
│ Upstream Time │ 백엔드 서버 응답 시간 │
│ (백엔드 응답 시간) │ 프록시 지연 vs 백엔드 지연 구분 │
├────────────────────┼───────────────────────────────────────────────┤
│ Active Connections │ 현재 활성 연결 수 │
│ (활성 연결) │ worker_connections 한계에 근접 시 경고 │
├────────────────────┼───────────────────────────────────────────────┤
│ Upstream Health │ 백엔드 서버 상태 (up/down) │
│ (백엔드 상태) │ down 서버 발생 시 즉시 알림 │
├────────────────────┼───────────────────────────────────────────────┤
│ SSL Certificate │ 인증서 만료까지 남은 일수 │
│ (인증서 만료) │ 30일 이하 = 경고, 7일 이하 = 긴급 │
├────────────────────┼───────────────────────────────────────────────┤
│ Bandwidth │ 인바운드/아웃바운드 트래픽량 │
│ (대역폭) │ 네트워크 용량 계획에 활용 │
└────────────────────┴───────────────────────────────────────────────┘
9.3 고가용성 (High Availability)
Active-Passive (keepalived 사용)
┌──────────────────────────────────────────────────────────────────┐
│ Active-Passive HA 구성 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ Virtual IP │ │
│ │ (VIP) │ │
│ │ 203.0.113.10 │ │
│ └────────┬────────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ │ │ │
│ v v │
│ ┌───────────────────┐ ┌───────────────────┐ │
│ │ Nginx (Active) │ │ Nginx (Passive) │ │
│ │ MASTER │ │ BACKUP │ │
│ │ Priority: 100 │ │ Priority: 50 │ │
│ │ 203.0.113.11 │ │ 203.0.113.12 │ │
│ └───────────────────┘ └───────────────────┘ │
│ │ │ │
│ │ keepalived VRRP │ │
│ │<────── 하트비트 ──────────>│ │
│ │
│ 정상 상태: VIP가 Active(Master)에 바인딩 │
│ 장애 발생: keepalived가 감지 → VIP가 Passive(Backup)로 이동 │
│ Failover 시간: 보통 1-3초 │
│ │
└──────────────────────────────────────────────────────────────────┘
Active-Active (DNS 라운드 로빈 또는 L4 LB)
┌──────────────────────────────────────────────────────────────────┐
│ Active-Active HA 구성 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ L4 Load │ │
│ │ Balancer │ │
│ │ (또는 DNS RR) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ v v v │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ Nginx Node 1 │ │ Nginx Node 2 │ │ Nginx Node 3 │ │
│ │ (Active) │ │ (Active) │ │ (Active) │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │
│ 모든 노드가 트래픽을 처리 │
│ 한 노드 장애 시 나머지 노드가 자동으로 전체 부하 처리 │
│ 확장성이 좋음 (노드 추가만으로 용량 증가) │
│ │
└──────────────────────────────────────────────────────────────────┘
9.4 트러블슈팅
502 Bad Gateway
┌──────────────────────────────────────────────────────────────────┐
│ 502 Bad Gateway 원인과 해결 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 원인 1: 백엔드 서버가 다운되었거나 응답 불가 │
│ 진단: curl -v http://backend-ip:port/health │
│ 해결: 백엔드 서비스 재시작, 프로세스 상태 확인 │
│ │
│ 원인 2: 백엔드 서버가 유효하지 않은 응답을 반환 │
│ 진단: nginx 에러 로그 확인 (upstream prematurely closed) │
│ 해결: 백엔드 애플리케이션 로그 확인, 메모리/CPU 상태 점검 │
│ │
│ 원인 3: upstream 서버 연결 실패 │
│ 진단: 네트워크 연결 확인 (telnet backend-ip port) │
│ 해결: 방화벽 규칙 확인, 백엔드 리스닝 포트 확인 │
│ │
│ 원인 4: 프록시 버퍼 크기 부족 │
│ 진단: 에러 로그에 "upstream sent too big header" 표시 │
│ 해결: proxy_buffer_size, proxy_buffers 값 증가 │
│ │
└──────────────────────────────────────────────────────────────────┘
504 Gateway Timeout
┌──────────────────────────────────────────────────────────────────┐
│ 504 Gateway Timeout 원인과 해결 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 원인 1: 백엔드 서버의 처리 시간이 너무 오래 걸림 │
│ 진단: upstream_response_time 로그 확인 │
│ 해결: proxy_read_timeout 값 증가 또는 백엔드 최적화 │
│ │
│ 원인 2: 백엔드 서버에 연결할 수 없음 (타임아웃) │
│ 진단: proxy_connect_timeout 시간 내 연결 실패 │
│ 해결: 네트워크 상태 확인, 백엔드 서버 과부하 여부 확인 │
│ │
│ 원인 3: 백엔드 커넥션 풀 고갈 │
│ 진단: 백엔드의 active connections 수 확인 │
│ 해결: 백엔드 max connections 증가, 커넥션 풀 설정 조정 │
│ │
│ 주의: 타임아웃 값을 무작정 늘리는 것은 근본 해결이 아님 │
│ 반드시 백엔드 성능 문제를 먼저 해결할 것 │
│ │
└──────────────────────────────────────────────────────────────────┘
9.5 보안 하드닝
# /etc/nginx/conf.d/security-hardening.conf
# 보안 강화 설정 모음
# ── 서버 정보 은닉 ──
server_tokens off; # 버전 정보 숨김
more_clear_headers Server; # Server 헤더 완전 제거
# (ngx_headers_more 모듈 필요)
# ── HSTS (HTTP Strict Transport Security) ──
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# ── 콘텐츠 보안 헤더 ──
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 "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
# ── Rate Limiting (계층별) ──
# 전역 Rate Limiting
limit_req_zone $binary_remote_addr zone=global:20m rate=50r/s;
# API Rate Limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
# 로그인 Rate Limiting (Brute Force 방어)
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
# ── HTTP 메서드 제한 ──
server {
# 허용된 메서드만 통과
if ($request_method !~ ^(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS)$) {
return 405;
}
# 관리자 영역 접근 제한
location /admin/ {
allow 10.0.0.0/8;
allow 192.168.0.0/16;
deny all;
}
# 숨겨야 할 파일 접근 차단
location ~ /\.(ht|git|env|svn) {
deny all;
return 404;
}
# 큰 요청 본문 제한 (업로드 공격 방어)
client_max_body_size 10m;
client_body_timeout 10s;
client_header_timeout 10s;
}
10. 현대적 활용
10.1 Kubernetes Ingress Controller
Kubernetes에서 외부 트래픽을 클러스터 내부 서비스로 전달하는 리버스 프록시의 Kubernetes 네이티브 구현이다.
┌──────────────────────────────────────────────────────────────────┐
│ Kubernetes Ingress Controller 구조 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌────────────────────────────────────────┐ │
│ │ Client │───>│ Kubernetes Cluster │ │
│ └────────┘ │ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ Ingress Controller │ │ │
│ │ │ (Nginx / Traefik / │ │ │
│ │ │ Envoy Gateway) │ │ │
│ │ └──────────┬───────────┘ │ │
│ │ │ │ │
│ │ ┌────────┼────────┐ │ │
│ │ v v v │ │
│ │ ┌──────┐┌──────┐┌──────┐ │ │
│ │ │ Svc ││ Svc ││ Svc │ │ │
│ │ │ A ││ B ││ C │ │ │
│ │ └──────┘└──────┘└──────┘ │ │
│ │ │ │
│ └────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
주요 Ingress Controller 비교
| 구현체 | 기반 프록시 | 특징 |
|---|---|---|
| ingress-nginx | Nginx | 가장 널리 사용, 안정적, Kubernetes 공식 |
| Traefik | Traefik | 자동 디스커버리, Let’s Encrypt 통합 |
| Envoy Gateway | Envoy | Gateway API 네이티브, CNCF 생태계 |
| Istio Gateway | Envoy | 서비스 메시와 통합, mTLS 자동 |
| AWS ALB Controller | AWS ALB | AWS 환경 최적화, 관리형 |
Gateway API (차세대 Ingress)
# Gateway API 예시 - Kubernetes의 차세대 트래픽 관리 표준
# Ingress 리소스보다 더 표현력이 풍부하고 역할 분리가 명확
# 인프라 관리자가 Gateway를 정의
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: dalmung-gateway
namespace: infra
spec:
gatewayClassName: envoy # 사용할 프록시 구현체
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- name: dalmung-tls-cert
---
# 애플리케이션 개발자가 HTTPRoute를 정의
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: dalmung-api-route
namespace: dalmung-app
spec:
parentRefs:
- name: dalmung-gateway
namespace: infra
hostnames:
- "api.dalmung.kr"
rules:
- matches:
- path:
type: PathPrefix
value: /api/v1/users
backendRefs:
- name: user-service
port: 8080
weight: 80 # 80% 트래픽
- name: user-service-canary
port: 8080
weight: 20 # 20% 카나리 배포
10.2 서비스 메시 (Service Mesh)
Istio + Envoy
┌──────────────────────────────────────────────────────────────────┐
│ Istio 서비스 메시 구조 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────┐ │
│ │ Control Plane (Istiod) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │
│ │ │ Pilot │ │ Citadel │ │ Galley │ │ │
│ │ │ (설정 배포)│ │ (인증서) │ │ (설정 검증) │ │ │
│ │ └──────────┘ └──────────┘ └──────────────┘ │ │
│ └──────────────────────┬─────────────────────────┘ │
│ │ xDS API │
│ v │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Data Plane │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Pod A │ │ Pod B │ │ │
│ │ │ ┌────┐┌────┐│ │ ┌────┐┌────┐ │ │ │
│ │ │ │App ││Envoy││───>│ │Envoy││App │ │ │ │
│ │ │ └────┘└────┘│mTLS│ └────┘└────┘ │ │ │
│ │ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 핵심 기능: │
│ - 자동 mTLS: 서비스 간 통신 암호화 │
│ - 트래픽 관리: 카나리 배포, A/B 테스트, 서킷 브레이커 │
│ - 관찰가능성: 분산 트레이싱, 메트릭 자동 수집 │
│ - 보안 정책: RBAC, 네트워크 정책 │
│ │
└──────────────────────────────────────────────────────────────────┘
Ambient Mesh (사이드카 없는 서비스 메시)
┌──────────────────────────────────────────────────────────────────┐
│ 기존 사이드카 vs Ambient Mesh │
├──────────────────────────────────────────────────────────────────┤
│ │
│ [기존 사이드카 방식] │
│ 각 Pod에 Envoy 사이드카 컨테이너가 함께 실행됨 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Pod │ │ Pod │ │ Pod │ │
│ │ ┌────┐┌────┐│ │ ┌────┐┌────┐│ │ ┌────┐┌────┐│ │
│ │ │App ││Envoy││ │ │App ││Envoy││ │ │App ││Envoy││ │
│ │ └────┘└────┘│ │ └────┘└────┘│ │ └────┘└────┘│ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ 문제: 메모리 오버헤드, 지연 시간 추가, 관리 복잡도 │
│ │
│ [Ambient Mesh 방식] │
│ 노드당 ztunnel(L4) + 필요 시 waypoint proxy(L7) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Pod │ │ Pod │ │ Pod │ │
│ │ ┌────┐ │ │ ┌────┐ │ │ ┌────┐ │ │
│ │ │App │ │ │ │App │ │ │ │App │ │ │
│ │ └────┘ │ │ └────┘ │ │ └────┘ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ v v v │
│ ┌──────────────────────────────────────────────┐ │
│ │ ztunnel (노드당 1개, L4 프록시) │ │
│ │ mTLS, L4 라우팅 담당 │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ 장점: 리소스 절약, 배포 간소화, 점진적 도입 가능 │
│ │
└──────────────────────────────────────────────────────────────────┘
10.3 API Gateway
| 솔루션 | 유형 | 특징 |
|---|---|---|
| Kong | 오픈소스/상용 | Nginx 기반, 플러그인 생태계, DB-less 모드 |
| AWS API Gateway | 관리형 | REST/WebSocket/HTTP API, Lambda 통합 |
| Apigee | 관리형 (Google) | API 분석, 개발자 포털, 기업용 |
| Tyk | 오픈소스/상용 | Go 기반, 경량, GraphQL 지원 |
API Gateway는 리버스 프록시의 기능에 API 관리 특화 기능을 추가한 것이다:
- API 키 관리 및 인증/인가
- 요청/응답 변환 (GraphQL-to-REST 등)
- API 버전 관리
- 개발자 포털 및 문서화
- 사용량 모니터링 및 과금
10.4 CDN (Content Delivery Network)
| CDN | 특징 |
|---|---|
| Cloudflare | 전 세계 300+ PoP, Workers(Edge Computing), 무료 플랜 제공 |
| AWS CloudFront | AWS 생태계 통합, Lambda@Edge, Origin Shield |
| Akamai | 가장 오래된 CDN, 기업용 특화, 330,000+ 서버 |
| Fastly | 실시간 퍼지, VCL 커스터마이징, Edge Computing |
CDN은 본질적으로 전 세계에 분산된 리버스 프록시 네트워크이다. 정적 콘텐츠 캐싱을 넘어 DDoS 방어, WAF, Edge Computing까지 제공하며, 현대 웹 인프라에서 가장 앞단의 리버스 프록시 역할을 수행한다.
10.5 제로 트러스트 (Zero Trust)
┌──────────────────────────────────────────────────────────────────┐
│ 제로 트러스트 아키텍처 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 핵심 원칙: "Never Trust, Always Verify" │
│ (절대 신뢰하지 말고, 항상 검증하라) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ [기존 모델: 경계 보안] │ │
│ │ │ │
│ │ 인터넷 ──> [방화벽] ──> 내부 네트워크 (신뢰 영역) │ │
│ │ 모든 내부 통신은 신뢰 │ │
│ │ 한 번 들어오면 자유롭게 접근 │ │
│ │ │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ [제로 트러스트 모델] │ │
│ │ │ │
│ │ 모든 요청 ──> [Identity Proxy] ──> 인증 + 인가 + 정책 │ │
│ │ 확인 후에만 접근 허용 │ │
│ │ │ │
│ │ 내부든 외부든 모든 요청을 검증 │ │
│ │ 최소 권한 원칙 적용 │ │
│ │ 마이크로세그멘테이션으로 횡이동 방지 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 리버스 프록시의 역할: │
│ - 모든 접근 요청의 중앙 검증 지점 │
│ - mTLS를 통한 서비스 간 신원 확인 │
│ - 세밀한 접근 정책(RBAC, ABAC) 적용 │
│ - 모든 트래픽의 로깅 및 감사 │
│ │
│ Gartner 예측: 2026년까지 대기업의 60%가 제로 트러스트를 채택 │
│ │
└──────────────────────────────────────────────────────────────────┘
11. 관련 기술 비교
11.1 Reverse Proxy vs Load Balancer
┌──────────────────────────────────────────────────────────────────┐
│ Reverse Proxy vs Load Balancer │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 관계: Reverse Proxy ⊇ Load Balancer │
│ (리버스 프록시가 로드 밸런서를 포함하는 상위 개념) │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Reverse Proxy │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ Load Balancer │ │ │
│ │ │ - 트래픽 분산 │ │ │
│ │ │ - 헬스 체크 │ │ │
│ │ │ - 세션 고정 │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ + SSL 터미네이션 │ │
│ │ + 캐싱 │ │
│ │ + 압축 │ │
│ │ + 보안 (WAF, Rate Limiting) │ │
│ │ + URL 재작성 │ │
│ │ + 헤더 조작 │ │
│ │ + 콘텐츠 기반 라우팅 │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
| 비교 항목 | Reverse Proxy | Load Balancer |
|---|---|---|
| 범위 | 광범위 (다기능) | 트래픽 분산 특화 |
| SSL 처리 | 지원 | 일부 지원 (L7 LB) |
| 캐싱 | 지원 | 미지원 |
| 콘텐츠 수정 | 가능 (헤더, 본문) | 불가 (L4) / 제한적 (L7) |
| 보안 기능 | WAF, IP 차단 등 | 기본 수준 |
| 단독 사용 | 가능 (백엔드 1대여도 유용) | 백엔드 2대 이상 시 의미 |
| 대표 제품 | Nginx, Envoy | F5, AWS NLB |
11.2 Reverse Proxy vs API Gateway
┌───────────────────┬────────────────────┬──────────────────────┐
│ 비교 항목 │ Reverse Proxy │ API Gateway │
├───────────────────┼────────────────────┼──────────────────────┤
│ 핵심 목적 │ 트래픽 라우팅 및 │ API 관리 및 │
│ │ 서버 보호 │ 개발자 경험 │
├───────────────────┼────────────────────┼──────────────────────┤
│ 인증/인가 │ 기본 수준 │ 고급 (OAuth, JWT, │
│ │ (IP 기반 등) │ API 키, OIDC) │
├───────────────────┼────────────────────┼──────────────────────┤
│ Rate Limiting │ IP 기반 │ API 키/사용자별 │
│ │ │ 플랜별 제한 │
├───────────────────┼────────────────────┼──────────────────────┤
│ 요청/응답 변환 │ 제한적 │ 강력 (GraphQL-REST, │
│ │ (헤더 수준) │ 프로토콜 변환) │
├───────────────────┼────────────────────┼──────────────────────┤
│ API 버전 관리 │ URL 라우팅으로 가능 │ 네이티브 지원 │
├───────────────────┼────────────────────┼──────────────────────┤
│ 개발자 포털 │ 없음 │ 지원 (문서, 테스트) │
├───────────────────┼────────────────────┼──────────────────────┤
│ 분석/모니터링 │ 기본 로그 │ API 사용량 분석, │
│ │ │ 과금 연동 │
├───────────────────┼────────────────────┼──────────────────────┤
│ 대표 제품 │ Nginx, HAProxy │ Kong, AWS API GW, │
│ │ │ Apigee │
└───────────────────┴────────────────────┴──────────────────────┘
11.3 Reverse Proxy vs CDN
┌───────────────────┬────────────────────┬──────────────────────┐
│ 비교 항목 │ Reverse Proxy │ CDN │
├───────────────────┼────────────────────┼──────────────────────┤
│ 배치 위치 │ Origin 서버 앞 │ 전 세계 Edge PoP │
├───────────────────┼────────────────────┼──────────────────────┤
│ 주요 목적 │ 서버 보호, 라우팅 │ 콘텐츠 가속, 캐싱 │
├───────────────────┼────────────────────┼──────────────────────┤
│ 지리적 분산 │ 단일/소수 위치 │ 글로벌 (수백 PoP) │
├───────────────────┼────────────────────┼──────────────────────┤
│ 캐싱 │ Origin 레벨 캐시 │ Edge 레벨 캐시 │
├───────────────────┼────────────────────┼──────────────────────┤
│ DDoS 방어 │ Origin 용량 한계 │ 글로벌 네트워크 용량 │
│ │ │ 으로 대규모 방어 │
├───────────────────┼────────────────────┼──────────────────────┤
│ 비용 │ 자체 운영 (저렴) │ 트래픽 기반 과금 │
├───────────────────┼────────────────────┼──────────────────────┤
│ 동적 콘텐츠 │ 모든 요청 처리 │ 정적 위주 │
│ │ │ (동적은 Origin 전달) │
├───────────────────┼────────────────────┼──────────────────────┤
│ 대표 제품 │ Nginx, HAProxy │ Cloudflare, │
│ │ │ CloudFront, Akamai │
└───────────────────┴────────────────────┴──────────────────────┘
실제 프로덕션에서는 CDN과 리버스 프록시를 함께 사용한다: CDN이 글로벌 Edge 역할, 리버스 프록시가 Origin 보호 역할.
11.4 Reverse Proxy vs Service Mesh
┌───────────────────┬────────────────────┬──────────────────────┐
│ 비교 항목 │ Reverse Proxy │ Service Mesh │
├───────────────────┼────────────────────┼──────────────────────┤
│ 트래픽 방향 │ North-South │ East-West │
│ │ (외부 → 내부) │ (서비스 간) │
├───────────────────┼────────────────────┼──────────────────────┤
│ 배치 방식 │ 중앙 집중 │ 분산 (사이드카 또는 │
│ │ (클러스터 앞단) │ 노드 에이전트) │
├───────────────────┼────────────────────┼──────────────────────┤
│ 관리 대상 │ 외부 진입 트래픽 │ 내부 서비스 간 통신 │
├───────────────────┼────────────────────┼──────────────────────┤
│ mTLS │ 클라이언트-프록시 │ 서비스 간 자동 mTLS │
├───────────────────┼────────────────────┼──────────────────────┤
│ 관찰가능성 │ 외부 트래픽 로그 │ 서비스 간 트레이싱, │
│ │ │ 메트릭, 로그 전체 │
├───────────────────┼────────────────────┼──────────────────────┤
│ 트래픽 제어 │ 라우팅, LB │ 서킷 브레이커, 재시도, │
│ │ │ 카나리, 미러링 │
├───────────────────┼────────────────────┼──────────────────────┤
│ 대표 제품 │ Nginx, HAProxy │ Istio, Linkerd, │
│ │ │ Consul Connect │
└───────────────────┴────────────────────┴──────────────────────┘
[North-South vs East-West 트래픽]
North-South (외부 ↔ 내부)
│
v
┌───────────────────────────────────────┐
│ Reverse Proxy │ <-- 여기를 관리
│ (Ingress Controller) │
└───────────────┬───────────────────────┘
│
┌───────────┼───────────┐
v v v
┌───────┐ ┌───────┐ ┌───────┐
│ Svc A │<>│ Svc B │<>│ Svc C │ East-West (서비스 ↔ 서비스)
└───────┘ └───────┘ └───────┘
^ ^ ^
└───────────┼───────────┘
│
Service Mesh <-- 여기를 관리
11.5 현대 아키텍처 전체 조감도
┌──────────────────────────────────────────────────────────────────────┐
│ 현대 웹 인프라 전체 구조 │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ [Internet] │
│ │ │
│ v │
│ ┌──────────────────────────┐ │
│ │ CDN │ Cloudflare, CloudFront │
│ │ (글로벌 리버스 프록시) │ 정적 캐시, DDoS 방어, Edge 처리 │
│ └────────────┬─────────────┘ │
│ │ │
│ v │
│ ┌──────────────────────────┐ │
│ │ Reverse Proxy / │ Nginx, HAProxy, Envoy Gateway │
│ │ API Gateway │ SSL 종료, 라우팅, 인증, Rate Limit │
│ └────────────┬─────────────┘ │
│ │ │
│ v │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Kubernetes Cluster │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ Service Mesh (Istio) │ │ │
│ │ │ │ │ │
│ │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │
│ │ │ │ Svc A │<──>│ Svc B │<──>│ Svc C │ │ │ │
│ │ │ │(+Envoy)│ │(+Envoy)│ │(+Envoy)│ │ │ │
│ │ │ └────────┘ └────────┘ └────────┘ │ │ │
│ │ │ mTLS, 트레이싱, 서킷 브레이커 │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
12. 요약
12.1 핵심 정리
리버스 프록시는 클라이언트와 백엔드 서버 사이에서 트래픽을 중재하는 핵심 인프라 컴포넌트이다. 로드 밸런싱, SSL 터미네이션, 캐싱, 보안, 압축 등 다양한 기능을 제공하며, 현대 웹 아키텍처에서 빠질 수 없는 필수 요소로 자리잡았다.
컨테이너와 마이크로서비스의 확산으로 리버스 프록시의 역할은 더욱 확장되어, Kubernetes Ingress Controller, Service Mesh의 데이터 플레인, API Gateway, CDN 등 다양한 형태로 진화하고 있다.
12.2 상황별 선택 가이드
┌──────────────────────────┬──────────────────────────────────────┐
│ 상황 │ 권장 솔루션 │
├──────────────────────────┼──────────────────────────────────────┤
│ 소규모 웹 서비스 │ Nginx 또는 Caddy │
│ (빠른 시작 필요) │ 간단한 설정, 자동 HTTPS │
├──────────────────────────┼──────────────────────────────────────┤
│ 고트래픽 서비스 │ Nginx + HAProxy │
│ (성능 최우선) │ 또는 HAProxy 단독 │
├──────────────────────────┼──────────────────────────────────────┤
│ Docker 환경 │ Traefik │
│ (자동 디스커버리 필요) │ Docker 라벨로 자동 설정 │
├──────────────────────────┼──────────────────────────────────────┤
│ Kubernetes 환경 │ ingress-nginx 또는 Envoy Gateway │
│ (클러스터 Ingress) │ Gateway API 지원 고려 │
├──────────────────────────┼──────────────────────────────────────┤
│ 마이크로서비스 간 통신 │ Istio + Envoy (Service Mesh) │
│ (East-West 트래픽) │ 또는 Ambient Mesh │
├──────────────────────────┼──────────────────────────────────────┤
│ API 관리가 핵심 │ Kong 또는 AWS API Gateway │
│ (인증, 과금, 분석) │ API Gateway 전용 솔루션 │
├──────────────────────────┼──────────────────────────────────────┤
│ 글로벌 서비스 │ Cloudflare 또는 AWS CloudFront │
│ (전 세계 사용자 대상) │ CDN + Origin 리버스 프록시 조합 │
├──────────────────────────┼──────────────────────────────────────┤
│ 클라우드 네이티브 │ AWS ALB/NLB, GCP Cloud LB │
│ (관리형 선호) │ 관리형 로드 밸런서 + 클라우드 서비스 │
├──────────────────────────┼──────────────────────────────────────┤
│ 제로 트러스트 환경 │ Envoy + Istio │
│ (최고 보안 수준) │ mTLS 자동, RBAC, 감사 로그 │
└──────────────────────────┴──────────────────────────────────────┘
12.3 키워드
Reverse Proxy, Forward Proxy, Load Balancing, SSL Termination,
Nginx, HAProxy, Envoy, Traefik, Caddy, Apache,
CDN, API Gateway, Service Mesh, Ingress Controller,
Gateway API, Istio, Ambient Mesh, xDS Protocol,
Zero Trust, mTLS, WAF, DDoS, Rate Limiting,
Round Robin, Least Connections, IP Hash, Consistent Hash,
Health Check, Connection Pooling, WebSocket Proxy,
Spoon-Feeding, Gzip, Brotli, Zstandard,
OWASP, PCI DSS, NIST, HSTS, OCSP Stapling,
Kubernetes, Docker, Cloud Native, CNCF, eBPF,
X-Forwarded-For, X-Real-IP, X-Forwarded-Proto, PROXY Protocol,
RFC 7239, Forwarded, CF-Connecting-IP, True-Client-IP,
set_real_ip_from, real_ip_recursive, proxy_set_header,
$proxy_add_x_forwarded_for, DSR, Via
13. 프록시 헤더 심층 분석 (X-Forwarded-For, X-Real-IP, X-Forwarded-Proto)
리버스 프록시가 클라이언트와 백엔드 사이에 위치하면, 백엔드 서버는 원래 클라이언트의 정보(IP, 프로토콜 등)를 잃어버린다. 이 문제를 해결하기 위해 등장한 다양한 프록시 헤더들의 역사, 동작 원리, 보안 고려사항, 그리고 올바른 사용법을 심층적으로 분석한다.
13.1 용어 사전 (Terminology)
┌──────────────────────────┬───────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────┐
│ 용어 │ 풀네임 / 의미 │ 유래 │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ X- (접두사) │ eXperimental / eXtension │ 1975년 Brian Harvey가 FTP에서 제안, RFC 822(1982)에서 공식화, │
│ │ │ RFC 6648(2012)에서 신규 헤더에 대해 deprecated │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ X-Forwarded-For (XFF) │ X-Forwarded-For │ X=비표준, Forwarded=전달됨, For=누구를 대신해서(on behalf of). │
│ │ │ "이 요청은 IP x.x.x.x를 대신하여 전달된 것" │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ X-Real-IP │ X-Real-IP │ "Real"=진짜 클라이언트 IP 하나만 전달. │
│ │ │ Nginx의 ngx_http_realip_module과 함께 등장. 어떠한 RFC도 없는 비표준 헤더 │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ X-Forwarded-Proto │ X-Forwarded-Proto │ Proto=Protocol. SSL Termination 후 백엔드가 원래 프로토콜(HTTPS)을 │
│ │ │ 알 수 없는 문제 해결 │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ X-Forwarded-Host │ X-Forwarded-Host │ 클라이언트가 원래 요청한 Host 헤더 값 보존 │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ X-Forwarded-Port │ X-Forwarded-Port │ 클라이언트가 연결한 원래 포트 번호 보존 │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ Forwarded (RFC 7239) │ Forwarded │ X-Forwarded-* 전체를 대체하는 IETF 표준 헤더. │
│ │ │ for/by/host/proto 4개 파라미터 │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ PROXY Protocol │ PROXY Protocol │ HAProxy가 2010년 설계한 TCP 레벨(L4) 프로토콜. │
│ │ │ 헤더가 아닌 TCP preamble │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ $remote_addr │ Nginx 변수 │ TCP 연결을 맺은 직접 상대방 IP. Spoofing 불가 │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ $proxy_add_x_forwarded │ Nginx 변수 │ 기존 XFF 헤더에 $remote_addr을 append. │
│ _for │ │ XFF 없으면 $remote_addr과 동일 │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ Via │ Via 헤더 │ RFC 7230 표준. 프록시 체인 기록용이나 원본 IP 미포함 │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ CF-Connecting-IP │ Cloudflare 전용 │ Cloudflare가 모든 플랜에서 자동 삽입하는 단일 클라이언트 IP 헤더 │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ True-Client-IP │ Akamai / Cloudflare Enterprise │ 단일 클라이언트 IP. Akamai 기원, │
│ │ │ Cloudflare Enterprise에서 레거시 호환용 │
├──────────────────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ DSR │ Direct Server Return │ 네트워크 레벨(L2/L3)에서 원본 IP를 보존하는 LB 아키텍처 패턴 │
└──────────────────────────┴───────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────┘
13.2 등장 배경 — 왜 만들어졌나?
핵심 문제: TCP 연결 정보의 소실
리버스 프록시(또는 로드 밸런서)가 클라이언트와 서버 사이에 위치하면, 백엔드 서버가 보는 TCP 연결 정보는 프록시의 IP이지 원래 클라이언트의 IP가 아니다.
[프록시 없는 환경]
클라이언트(1.1.1.1) ──── TCP ────> 백엔드 서버
└─ REMOTE_ADDR = 1.1.1.1 ✓
[프록시가 있는 환경]
클라이언트(1.1.1.1) ──> 프록시(2.2.2.2) ──── TCP ────> 백엔드 서버
└─ REMOTE_ADDR = 2.2.2.2 ✗
(프록시 IP만 보임!)
이로 인해 발생하는 문제
┌────────────────────────────────┬──────────────────────────────────────────────────┐
│ 문제 │ 설명 │
├────────────────────────────────┼──────────────────────────────────────────────────┤
│ IP 기반 접근 제어 불가 │ 허용/차단 IP 목록이 프록시 IP만 매칭되어 무의미 │
├────────────────────────────────┼──────────────────────────────────────────────────┤
│ 로그에 프록시 IP만 기록 │ 수천 명의 요청이 모두 동일 IP로 기록됨 │
├────────────────────────────────┼──────────────────────────────────────────────────┤
│ Rate Limiting 오작동 │ 다수의 클라이언트가 하나의 IP로 집계되어 오탐/미탐 │
├────────────────────────────────┼──────────────────────────────────────────────────┤
│ 감사 추적(Audit Trail) 불가능 │ 법적/보안 요구사항인 사용자별 추적이 불가능 │
├────────────────────────────────┼──────────────────────────────────────────────────┤
│ 지리적 라우팅 실패 │ GeoIP 기반 콘텐츠 분배가 프록시 위치를 기준으로 동작 │
└────────────────────────────────┴──────────────────────────────────────────────────┘
해결 시도의 역사
┌──────────────────────────┬────────────────────────────────────────────────────────┐
│ 시대 │ 해결 방법 │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ 직접 연결 시대 │ REMOTE_ADDR만으로 충분 — 프록시가 없었음 │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ 초기 프록시 시대 │ 벤더별 독자 헤더 (Client-IP 등) — 상호 호환성 없음 │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ Squid의 X-Forwarded-For │ HTTP 헤더 방식의 사실상 표준 등장 (1996년경) │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ HAProxy PROXY Protocol │ TCP 레벨(L4) 해결 — 비-HTTP 프로토콜도 지원 (2010년) │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ RFC 7239 Forwarded │ IETF 공식 표준 — X-Forwarded-* 대체 시도 (2014년) │
├──────────────────────────┼────────────────────────────────────────────────────────┤
│ 2026 현재 │ RFC 7239 채택률 저조, X-Forwarded-* 가 여전히 de facto │
└──────────────────────────┴────────────────────────────────────────────────────────┘
SSL Termination과 X-Forwarded-Proto
리버스 프록시에서 SSL을 종료(SSL Termination)하면, 백엔드는 HTTP 요청만 받게 된다. 이때 백엔드가 원래 클라이언트가 HTTPS로 접속했는지 알 수 없으므로, HTTPS 리다이렉트 판단이 불가능해진다. 이 문제를 해결하기 위해 X-Forwarded-Proto가 필요하다.
클라이언트 ──HTTPS──> 리버스 프록시 ──HTTP──> 백엔드
(SSL Termination) └─ "이게 HTTP인가 HTTPS인가?"
└─ X-Forwarded-Proto: https 로 판단
13.3 역사적 기원 (Historical Origins)
X-Forwarded-For
- 기원: Squid Caching Proxy 팀이 도입 (1996년경)
- 배경: Squid 1.0.0이 1996년 7월에 릴리스되었으며, 캐시 프록시 환경에서 원본 클라이언트 IP를 보존할 방법이 필요했다
- 특징: 어떤 RFC에도 정의되지 않은 비표준 헤더이지만, 사실상 모든 프록시/로드 밸런서가 지원하는 de facto 표준이 되었다
- 포맷:
X-Forwarded-For: client, proxy1, proxy2(쉼표로 구분된 IP 체인)
X-Real-IP
- 기원: Nginx 생태계에서 등장 (Igor Sysoev, 2004년)
- 배경: Nginx 0.1.0(2004년 10월)의
ngx_http_realip_module과 함께 도입 - 특징: RFC가 전혀 없으며, 공식 스펙 문서도 없는 순수 비표준 헤더
- 설계 의도: XFF의 체인 방식 대신, 단일 IP 값만 전달하여 파싱을 단순화
X-Forwarded-Proto
- 기원: 로드 밸런서 업계의 관행에서 자연 발생 (2000년대)
- 배경: SSL Termination이 보편화되면서 원래 프로토콜 정보 전달의 필요성 대두
- 정착: AWS ELB(2009년 출시)가 X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Port를 자동 추가하면서 업계 표준으로 확립
Forwarded (RFC 7239)
- 기원: Andreas Petersson과 Martin Nilsson (Opera Software)이 IETF에 초안 제출 (2011년 11월)
- 표준화: 2014년 6월 RFC 7239로 발행
- 목적: X-Forwarded-* 전체를 하나의 표준 헤더로 통합
- 현실: 2026년 현재까지도 채택률이 낮으며, X-Forwarded-*가 압도적으로 우세
13.4 학술적/이론적 배경 (Academic Foundation)
관련 RFC 요약
┌──────────────────┬──────────────────────────────────┬───────────┬──────────────────────────────────────────────────┐
│ RFC │ 제목 │ 발행 연도 │ 핵심 내용 │
├──────────────────┼──────────────────────────────────┼───────────┼──────────────────────────────────────────────────┤
│ RFC 2616 │ HTTP/1.1 │ 1999 │ Via 헤더 정의 (Section 14.45), │
│ │ │ │ 원본 IP 전달 메커니즘은 미표준화 │
├──────────────────┼──────────────────────────────────┼───────────┼──────────────────────────────────────────────────┤
│ RFC 6648 │ Deprecating "X-" Prefix │ 2012 │ 새 헤더에 X- 접두사 사용 금지 권고 (BCP 178) │
├──────────────────┼──────────────────────────────────┼───────────┼──────────────────────────────────────────────────┤
│ RFC 7230-7235 │ HTTP/1.1 재정비 │ 2014 │ RFC 2616 obsolete, Via 헤더 규정 명확화 │
├──────────────────┼──────────────────────────────────┼───────────┼──────────────────────────────────────────────────┤
│ RFC 7239 │ Forwarded HTTP Extension │ 2014 │ X-Forwarded-* 대체 표준 헤더. │
│ │ │ │ for/by/host/proto 파라미터 │
├──────────────────┼──────────────────────────────────┼───────────┼──────────────────────────────────────────────────┤
│ RFC 9110/9112 │ HTTP Semantics / HTTP/1.1 │ 2022 │ RFC 7230-7235 obsolete, 최신 HTTP 스펙 │
│ /9113 │ / HTTP/2 │ │ │
├──────────────────┼──────────────────────────────────┼───────────┼──────────────────────────────────────────────────┤
│ PROXY Protocol │ HAProxy 사실상 표준 │ 2010 │ TCP 레벨 텍스트 기반 클라이언트 IP 전달 │
│ v1 │ │ │ │
├──────────────────┼──────────────────────────────────┼───────────┼──────────────────────────────────────────────────┤
│ PROXY Protocol │ HAProxy 사실상 표준 │ 2014 │ 바이너리 포맷, TLV 확장 지원 │
│ v2 │ │ │ │
└──────────────────┴──────────────────────────────────┴───────────┴──────────────────────────────────────────────────┘
RFC 7239 Forwarded 헤더 구조
RFC 7239는 X-Forwarded-* 계열 헤더를 단일 표준 헤더로 통합한다. 4개의 파라미터를 제공한다:
Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43;host=example.com
┌───────────┬────────────────────────────────────────────────────────────┐
│ 파라미터 │ 설명 │
├───────────┼────────────────────────────────────────────────────────────┤
│ for │ 요청을 보낸 클라이언트 IP (X-Forwarded-For 대체) │
├───────────┼────────────────────────────────────────────────────────────┤
│ by │ 프록시 자신의 식별자 (인터페이스 IP 또는 토큰) │
├───────────┼────────────────────────────────────────────────────────────┤
│ host │ 원래 Host 헤더 값 (X-Forwarded-Host 대체) │
├───────────┼────────────────────────────────────────────────────────────┤
│ proto │ 원래 프로토콜 (X-Forwarded-Proto 대체) │
└───────────┴────────────────────────────────────────────────────────────┘
X-Forwarded-* 대비 개선점:
- 단일 헤더: 3~4개의 개별 헤더 대신 하나의 구조화된 헤더로 통합
- 난독화 지원:
for=_secret형태로 IP를 숨기면서도 프록시 체인 추적 가능 (프라이버시) - by 파라미터: 프록시 자체 식별이 가능하여 체인 디버깅 용이
- 표준화된 포맷: 파싱 규칙이 RFC에 명확히 정의되어 상호 운용성 향상
13.5 진화 타임라인 (Evolution Timeline)
┌────────────┬──────────────────────────────────────────────────────────────┐
│ 연도 │ 사건 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 1975 │ Brian Harvey, FTP에서 "X" 접두사 관행 제안 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 1982 │ RFC 822에서 X- 접두사 공식화 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 1996.07 │ Squid 1.0.0 릴리스, X-Forwarded-For 도입 추정 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 1999.06 │ RFC 2616 발행 — Via 헤더 정의, 원본 IP 전달은 미표준화 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2001 │ HAProxy 최초 개발 (Willy Tarreau) │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2002 │ Squid 2.4 X-Forwarded-For 패치 공식화 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2002 │ Nginx 개발 시작 (Igor Sysoev, Rambler C10K 해결) │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2004.10 │ Nginx 0.1.0 공개, ngx_http_realip_module + X-Real-IP │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2009.05 │ AWS ELB 출시 — X-Forwarded-For/Proto/Port 자동 추가 정착 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2010.10 │ HAProxy PROXY Protocol v1 발표 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2011.11 │ Opera의 Andreas Petersson, IETF에 Forwarded 초안 제출 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2012.06 │ RFC 6648 발행 — X- 접두사 신규 사용 폐기 권고 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2014.05 │ HAProxy PROXY Protocol v2 발표 (바이너리 포맷) │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2014.06 │ RFC 7239 발행 — Forwarded 표준 헤더 제정 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2015 │ RFC 7540 (HTTP/2) — 프록시 헤더 의미는 유지 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2019.04 │ Azure Front Door GA — X-Forwarded-* 지원 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2022.06 │ RFC 9110/9112/9113 — HTTP 최신 스펙, Via 규정 유지 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 2026 현재 │ RFC 7239 채택률 여전히 낮음, X-Forwarded-*가 de facto 표준 │
└────────────┴──────────────────────────────────────────────────────────────┘
13.6 대안 비교 (Alternatives & Trade-offs)
방법별 특성 비교
┌─────────────────────┬────────┬───────────────┬──────────────┬──────────────┬────────────┐
│ 방법 │ Layer │ 표준화 여부 │ Spoofing 방지 │ 다중 프록시 │ 비-HTTP │
├─────────────────────┼────────┼───────────────┼──────────────┼──────────────┼────────────┤
│ X-Forwarded-For │ L7 │ 비표준(de-facto)│ 취약 │ O (체인 기록) │ X │
├─────────────────────┼────────┼───────────────┼──────────────┼──────────────┼────────────┤
│ X-Real-IP │ L7 │ 비표준(Nginx) │ 취약 │ X (단일 값) │ X │
├─────────────────────┼────────┼───────────────┼──────────────┼──────────────┼────────────┤
│ Forwarded (RFC 7239)│ L7 │ IETF 표준 │ 취약 (동일) │ O (체인 기록) │ X │
├─────────────────────┼────────┼───────────────┼──────────────┼──────────────┼────────────┤
│ PROXY Protocol │ L4 │ HAProxy 사실상 │ 강함 (TCP) │ O │ O (모든TCP) │
├─────────────────────┼────────┼───────────────┼──────────────┼──────────────┼────────────┤
│ Via │ L7 │ IETF RFC 7230 │ 해당없음 │ O (hostname) │ X │
├─────────────────────┼────────┼───────────────┼──────────────┼──────────────┼────────────┤
│ True-Client-IP │ L7 │ 비표준(벤더) │ 중간 (CDN) │ X (단일 값) │ X │
├─────────────────────┼────────┼───────────────┼──────────────┼──────────────┼────────────┤
│ CF-Connecting-IP │ L7 │ 비표준(CF) │ 중간 (CF IP) │ X (단일 값) │ X │
├─────────────────────┼────────┼───────────────┼──────────────┼──────────────┼────────────┤
│ DSR │ L2/L3 │ 아키텍처 패턴 │ 매우 강함 │ 제한적 │ O │
└─────────────────────┴────────┴───────────────┴──────────────┴──────────────┴────────────┘
소프트웨어별 지원 매트릭스
┌─────────────────────┬───────┬────────┬─────────┬─────┬────────────┬────────┬───────┐
│ 방법 │ Nginx │ Apache │ HAProxy │ AWS │ Cloudflare │ Akamai │ Envoy │
├─────────────────────┼───────┼────────┼─────────┼─────┼────────────┼────────┼───────┤
│ X-Forwarded-For │ O │ O │ O │ O │ O │ O │ O │
├─────────────────────┼───────┼────────┼─────────┼─────┼────────────┼────────┼───────┤
│ X-Real-IP │ O │ 제한적 │ 수동 │ 제한│ X │ X │ X │
├─────────────────────┼───────┼────────┼─────────┼─────┼────────────┼────────┼───────┤
│ Forwarded (RFC 7239)│ 제한* │ 제한* │ O(생성) │ 제한│ X │ X │ O │
├─────────────────────┼───────┼────────┼─────────┼─────┼────────────┼────────┼───────┤
│ PROXY Protocol v1/v2│ O(수신)│ 제한적 │ O │ NLB │ X │ X │ O │
├─────────────────────┼───────┼────────┼─────────┼─────┼────────────┼────────┼───────┤
│ True-Client-IP │ X │ X │ X │ X │ Enterprise │ O │ X │
├─────────────────────┼───────┼────────┼─────────┼─────┼────────────┼────────┼───────┤
│ CF-Connecting-IP │ X │ X │ X │ X │ O(전체) │ X │ X │
├─────────────────────┼───────┼────────┼─────────┼─────┼────────────┼────────┼───────┤
│ DSR │ - │ - │ O │ NLB │ X │ X │ X │
└─────────────────────┴───────┴────────┴─────────┴─────┴────────────┴────────┴───────┘
* Nginx/Apache에서 RFC 7239 Forwarded 생성 시 복잡한 map/정규식 workaround 필요
13.7 상황별 최적 선택 (When Each is Effective)
시나리오별 권장 설정
┌──────────────────────────────┬─────────────────────────────────┬──────────────────────────┐
│ 시나리오 │ 권장 방법 │ 핵심 설정 │
├──────────────────────────────┼─────────────────────────────────┼──────────────────────────┤
│ 단일 Nginx 프록시 │ X-Real-IP + X-Forwarded-For │ proxy_set_header │
├──────────────────────────────┼─────────────────────────────────┼──────────────────────────┤
│ 다중 프록시 체인 (CDN→LB→App) │ real_ip_recursive on + 신뢰CIDR │ set_real_ip_from │
├──────────────────────────────┼─────────────────────────────────┼──────────────────────────┤
│ K8s Nginx Ingress │ ConfigMap forwarded-headers │ proxy-real-ip-cidr │
├──────────────────────────────┼─────────────────────────────────┼──────────────────────────┤
│ K8s Traefik │ forwardedHeaders.trustedIPs │ insecure 금지 │
├──────────────────────────────┼─────────────────────────────────┼──────────────────────────┤
│ K8s Istio/Envoy │ numTrustedProxies │ x-envoy-external-address │
├──────────────────────────────┼─────────────────────────────────┼──────────────────────────┤
│ AWS ALB │ 기본 XFF 자동 추가 │ 별도 설정 불필요 │
├──────────────────────────────┼─────────────────────────────────┼──────────────────────────┤
│ AWS NLB │ PROXY Protocol v2 │ listen proxy_protocol │
├──────────────────────────────┼─────────────────────────────────┼──────────────────────────┤
│ Cloudflare → Nginx │ CF-Connecting-IP │ Cloudflare IP set_real_ip│
├──────────────────────────────┼─────────────────────────────────┼──────────────────────────┤
│ WebSocket │ Upgrade/Connection 명시 전달 │ proxy_http_version 1.1 │
├──────────────────────────────┼─────────────────────────────────┼──────────────────────────┤
│ gRPC/HTTP2 │ grpc_set_header 사용 │ listen http2 │
├──────────────────────────────┼─────────────────────────────────┼──────────────────────────┤
│ TCP non-HTTP │ PROXY Protocol (유일한 방법) │ 양단 모두 활성화 │
├──────────────────────────────┼─────────────────────────────────┼──────────────────────────┤
│ Spring Boot │ forward-headers-strategy= │ ForwardedHeaderFilter │
│ │ FRAMEWORK │ │
└──────────────────────────────┴─────────────────────────────────┴──────────────────────────┘
주요 설정 예제
다중 프록시 체인 (CDN → LB → App):
# CDN과 내부 LB를 신뢰 프록시로 등록
set_real_ip_from 10.0.0.0/8; # 내부 네트워크
set_real_ip_from 172.16.0.0/12; # 내부 네트워크
set_real_ip_from 203.0.113.0/24; # CDN 엣지 서버
# XFF 헤더에서 원본 IP를 추출
real_ip_header X-Forwarded-For;
# 신뢰 프록시 IP를 건너뛰고 진짜 클라이언트 IP를 재귀적으로 탐색
real_ip_recursive on;
동작 원리:
XFF: 1.1.1.1, 203.0.113.5, 10.0.1.2
↑ 클라이언트 ↑ CDN ↑ LB
real_ip_recursive on 동작:
1) 10.0.1.2 → set_real_ip_from에 포함 → 건너뜀
2) 203.0.113.5 → set_real_ip_from에 포함 → 건너뜀
3) 1.1.1.1 → 신뢰 범위 밖 → 이것이 진짜 클라이언트 IP
→ $remote_addr = 1.1.1.1
Cloudflare → Nginx:
# Cloudflare IP 범위 (https://cloudflare.com/ips/ 에서 최신 확인)
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
# IPv6
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2c0f:f248::/32;
set_real_ip_from 2a06:98c0::/29;
# Cloudflare 전용 헤더 사용 (XFF보다 신뢰성 높음)
real_ip_header CF-Connecting-IP;
real_ip_recursive on;
AWS NLB + PROXY Protocol:
server {
# NLB가 PROXY Protocol v2 preamble을 전송
listen 80 proxy_protocol;
# NLB 내부 IP를 신뢰 범위로 설정
set_real_ip_from 10.0.0.0/8;
# PROXY Protocol에서 원본 IP 추출
real_ip_header proxy_protocol;
location / {
# 백엔드에 원본 IP 전달
proxy_set_header X-Real-IP $proxy_protocol_addr;
proxy_set_header X-Forwarded-For $proxy_protocol_addr;
proxy_pass http://backend;
}
}
Spring Boot 설정:
# application.properties
server.forward-headers-strategy=FRAMEWORK
// ForwardedHeaderFilter 등록 (Spring Boot 자동 구성이 아닌 수동 등록 시)
@Bean
public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
FilterRegistrationBean<ForwardedHeaderFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new ForwardedHeaderFilter());
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
주의:
forward-headers-strategy=FRAMEWORK을 사용하면 Spring이 X-Forwarded-* 헤더를 신뢰한다. 프록시 뒤에서만 사용해야 하며, 직접 인터넷에 노출된 서버에서는 Spoofing 위험이 있다.
13.8 베스트 프랙티스 (Best Practices)
Nginx default.conf 권장 설정
# ============================================================
# Edge Proxy (인터넷 직접 수신 — 첫 번째 프록시)
# ============================================================
# 핵심: 클라이언트가 조작한 XFF를 덮어씀 (spoofing 방지)
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
# ============================================================
# 내부 프록시 (체인 중간 — 두 번째 이후 프록시)
# ============================================================
# 핵심: 기존 XFF에 현재 hop IP를 append
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
차이점 핵심: Edge에서는
$remote_addr로 덮어쓰고, 내부에서는$proxy_add_x_forwarded_for로 추가한다. Edge에서 덮어쓰지 않으면 클라이언트가 XFF에 가짜 IP를 삽입할 수 있다.
보안 베스트 프랙티스
┌────┬──────────────────────────────────────────────────────────────────────┐
│ # │ 보안 규칙 │
├────┼──────────────────────────────────────────────────────────────────────┤
│ 1 │ Edge proxy에서 XFF를 $remote_addr로 덮어쓰기 (spoofing 차단) │
│ │ → 클라이언트가 보낸 가짜 XFF 값을 무시하고 TCP 연결 IP만 기록 │
├────┼──────────────────────────────────────────────────────────────────────┤
│ 2 │ set_real_ip_from으로 신뢰 프록시 CIDR 명시 │
│ │ → 신뢰하지 않는 IP에서 온 XFF는 realip 모듈이 무시 │
├────┼──────────────────────────────────────────────────────────────────────┤
│ 3 │ real_ip_recursive on 활성화 │
│ │ → 다중 프록시 환경에서 신뢰 IP를 재귀적으로 건너뛰어 진짜 IP 추출 │
├────┼──────────────────────────────────────────────────────────────────────┤
│ 4 │ CDN IP 대역 자동 업데이트 (cron 스크립트) │
│ │ → Cloudflare, AWS 등의 IP 대역은 주기적으로 변경됨 │
├────┼──────────────────────────────────────────────────────────────────────┤
│ 5 │ 로그 포맷에 $http_x_forwarded_for 포함 │
│ │ → 장애/보안 분석 시 원본 IP 추적 가능 │
└────┴──────────────────────────────────────────────────────────────────────┘
CDN IP 대역 자동 업데이트 cron 예시:
#!/bin/bash
# /etc/cron.daily/update-cloudflare-ips.sh
CONF="/etc/nginx/conf.d/cloudflare-realip.conf"
echo "# Auto-generated: $(date)" > "$CONF"
for ip in $(curl -s https://www.cloudflare.com/ips-v4); do
echo "set_real_ip_from $ip;" >> "$CONF"
done
for ip in $(curl -s https://www.cloudflare.com/ips-v6); do
echo "set_real_ip_from $ip;" >> "$CONF"
done
echo 'real_ip_header CF-Connecting-IP;' >> "$CONF"
nginx -t && systemctl reload nginx
로그 포맷 권장 설정:
log_format proxy_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'xff="$http_x_forwarded_for" '
'real_ip="$http_x_real_ip" '
'proto="$http_x_forwarded_proto"';
access_log /var/log/nginx/access.log proxy_log;
13.9 안티패턴 (Anti-Patterns)
┌────┬────────────────────────────────────────────────────────────────────────────────┐
│ # │ 안티패턴 │
├────┼────────────────────────────────────────────────────────────────────────────────┤
│ ❌ │ XFF의 첫 번째(leftmost) IP를 무조건 신뢰 │
│ │ → 클라이언트가 요청 시 X-Forwarded-For: fake_ip 를 삽입 가능 │
│ │ → 해결: rightmost-first 파싱 (신뢰 프록시 IP를 오른쪽에서 제거하며 탐색) │
├────┼────────────────────────────────────────────────────────────────────────────────┤
│ ❌ │ set_real_ip_from 없이 real_ip_header 사용 │
│ │ → 신뢰 범위가 지정되지 않으면 realip 모듈이 올바르게 동작하지 않음 │
│ │ → 해결: 반드시 set_real_ip_from으로 신뢰 프록시 IP 대역 명시 │
├────┼────────────────────────────────────────────────────────────────────────────────┤
│ ❌ │ X-Forwarded-Proto 미설정 → HTTPS redirect 무한 루프 │
│ │ → SSL termination 환경에서 백엔드가 HTTP로 판단 → 302 HTTPS → 프록시가 다시 │
│ │ HTTP로 전달 → 무한 반복 │
│ │ → 해결: proxy_set_header X-Forwarded-Proto $scheme; 필수 설정 │
├────┼────────────────────────────────────────────────────────────────────────────────┤
│ ❌ │ proxy_set_header와 add_header 혼용 │
│ │ → proxy_set_header: upstream(백엔드)으로 보내는 요청 헤더 설정 │
│ │ → add_header: 클라이언트에게 보내는 응답 헤더 추가 │
│ │ → 용도가 완전히 다름. 혼동 시 헤더가 엉뚱한 방향으로 전달됨 │
├────┼────────────────────────────────────────────────────────────────────────────────┤
│ ❌ │ 개발/운영 환경의 프록시 설정 불일치 │
│ │ → 개발 환경에서는 프록시가 없거나 다른 구성 → 운영 배포 시 IP 관련 버그 발생 │
│ │ → 해결: 환경별 include 파일 분리 (include /etc/nginx/conf.d/realip-*.conf;) │
├────┼────────────────────────────────────────────────────────────────────────────────┤
│ ❌ │ 로그에 프록시 IP만 기록 │
│ │ → $remote_addr만 기록하면 프록시 IP만 남아 분석 불가 │
│ │ → 해결: $http_x_forwarded_for 포함한 로그 포맷 사용 │
└────┴────────────────────────────────────────────────────────────────────────────────┘
13.10 Nginx 변수 동작 상세
시나리오: 다중 프록시 환경에서의 변수 값 추적
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 클라이언트 │────>│ 프록시 A │────>│ Nginx │────>│ 백엔드 │
│ 1.1.1.1 │ │ 2.2.2.2 │ │ 3.3.3.3 │ │ 4.4.4.4 │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
프록시 A가 XFF를 설정한 경우:
프록시 A → Nginx 요청:
X-Forwarded-For: 1.1.1.1
Nginx에서의 변수 값:
$remote_addr = 2.2.2.2 (TCP 연결 상대방 = 프록시 A)
$http_x_forwarded_for = 1.1.1.1 (프록시 A가 설정한 값)
$proxy_add_x_forwarded_for = 1.1.1.1, 2.2.2.2 (기존 XFF + $remote_addr)
proxy_set_header에 따른 백엔드 수신 값:
# 방법 1: $proxy_add_x_forwarded_for (체인 누적)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
→ 백엔드 수신: X-Forwarded-For: 1.1.1.1, 2.2.2.2
# 방법 2: $remote_addr (Edge에서 덮어쓰기)
proxy_set_header X-Forwarded-For $remote_addr;
→ 백엔드 수신: X-Forwarded-For: 2.2.2.2
→ (원본 IP 1.1.1.1 소실 — Edge가 아닌 경우 부적절)
XFF가 없는 경우 (첫 번째 프록시):
클라이언트 → Nginx 직접 연결 (프록시 없음):
$remote_addr = 1.1.1.1
$http_x_forwarded_for = (비어있음)
$proxy_add_x_forwarded_for = 1.1.1.1 (XFF 없으면 $remote_addr과 동일)
realip 모듈 적용 시 변수 변화
설정:
set_real_ip_from 2.2.2.2;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
적용 전:
$remote_addr = 2.2.2.2
적용 후:
$remote_addr = 1.1.1.1 (XFF에서 추출한 진짜 클라이언트 IP)
$realip_remote_addr = 2.2.2.2 (원래 TCP 연결 IP 보존)
13.11 Forwarded (RFC 7239) Nginx 구현
Nginx는 RFC 7239 Forwarded 헤더를 네이티브로 생성하지 않으므로,
map 디렉티브를 활용한 workaround가 필요하다.
# IPv4와 IPv6를 구분하여 RFC 7239 포맷으로 변환
map $remote_addr $proxy_forwarded_elem {
# IPv4: for=192.0.2.1
~^[0-9.]+$ "for=$remote_addr";
# IPv6: for="[2001:db8::1]" (대괄호 + 따옴표 필수)
~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\"";
# 기타: for=unknown
default "for=unknown";
}
# 기존 Forwarded 헤더가 있으면 append, 없으면 새로 생성
map $http_forwarded $proxy_add_forwarded {
"" "$proxy_forwarded_elem";
default "$http_forwarded, $proxy_forwarded_elem";
}
server {
listen 80;
server_name example.com;
location / {
# RFC 7239 표준 헤더
proxy_set_header Forwarded "$proxy_forwarded_elem;proto=$scheme";
# 하위 호환: 기존 X-Forwarded-* 헤더 병행 유지
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_pass http://backend;
}
}
생성되는 헤더 예시:
# IPv4 클라이언트, HTTPS 접속
Forwarded: for=203.0.113.50;proto=https
# IPv6 클라이언트, HTTP 접속
Forwarded: for="[2001:db8::1]";proto=http
# 다중 프록시 체인 (append 시)
Forwarded: for=203.0.113.50;proto=https, for=10.0.1.2;proto=http
참고: Envoy, HAProxy 등은 RFC 7239 Forwarded 헤더를 네이티브로 지원한다. Nginx에서 완벽한 RFC 7239 구현이 필요하면 OpenResty(Lua) 또는 njs 모듈 사용을 고려할 수 있다.
13.12 PROXY Protocol 상세
PROXY Protocol은 HTTP 헤더가 아닌 TCP 연결 시작 시 전송되는 preamble이다. L4(TCP) 레벨에서 동작하므로 HTTP 외의 프로토콜(MySQL, SMTP, WebSocket 등)에서도 사용할 수 있다.
v1 (텍스트 포맷)
PROXY TCP4 203.0.113.50 192.168.1.10 12345 8080\r\n
│ │ │ │ │ │
│ │ │ │ │ └─ 목적지 포트
│ │ │ │ └─ 출발지 포트
│ │ │ └─ 목적지 IP (프록시/서버)
│ │ └─ 출발지 IP (클라이언트)
│ └─ 프로토콜 (TCP4 또는 TCP6)
└─ 시그니처
- TCP 연결 수립 직후, HTTP 요청 전에 단 한 줄이 전송됨
- 텍스트 기반으로 디버깅이 쉬움
- 최대 108 바이트
v2 (바이너리 포맷)
v2는 바이너리 포맷으로 개선되어 다음과 같은 장점을 제공한다:
┌─────────────────────┬──────────────────────────────────────────────────┐
│ 특성 │ 설명 │
├─────────────────────┼──────────────────────────────────────────────────┤
│ 12바이트 시그니처 │ "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54 │
│ │ \x0A" — 텍스트와 명확히 구분 │
├─────────────────────┼──────────────────────────────────────────────────┤
│ TLV 확장 │ Type-Length-Value 구조로 추가 정보 전달 가능 │
│ │ (SSL 인증서 정보, AWS VPC Endpoint ID 등) │
├─────────────────────┼──────────────────────────────────────────────────┤
│ AF_UNIX / UDP 지원 │ TCP 외 소켓 타입도 지원 │
├─────────────────────┼──────────────────────────────────────────────────┤
│ 파싱 성능 │ 고정 길이 필드로 텍스트 파싱보다 빠름 │
├─────────────────────┼──────────────────────────────────────────────────┤
│ Spoofing 방지 │ TCP preamble이므로 HTTP 헤더처럼 클라이언트가 │
│ │ 임의로 조작할 수 없음 (TCP 연결 주체만 전송 가능) │
└─────────────────────┴──────────────────────────────────────────────────┘
v1 vs v2 비교
┌──────────────┬──────────────────┬──────────────────┐
│ 항목 │ v1 │ v2 │
├──────────────┼──────────────────┼──────────────────┤
│ 포맷 │ 텍스트(ASCII) │ 바이너리 │
├──────────────┼──────────────────┼──────────────────┤
│ 크기 │ 최대 108 바이트 │ 최소 16 바이트 │
├──────────────┼──────────────────┼──────────────────┤
│ 확장성 │ 없음 │ TLV 확장 가능 │
├──────────────┼──────────────────┼──────────────────┤
│ 프로토콜 지원 │ TCP4, TCP6 │ TCP, UDP, UNIX │
├──────────────┼──────────────────┼──────────────────┤
│ 디버깅 │ 쉬움 (텍스트) │ 어려움 (바이너리) │
├──────────────┼──────────────────┼──────────────────┤
│ 채택도 │ 레거시 │ 권장 (현대 표준) │
└──────────────┴──────────────────┴──────────────────┘
중요: PROXY Protocol은 양단(sender/receiver) 모두 활성화해야 한다. 한쪽만 활성화하면 preamble이 HTTP 요청으로 오해되어
400 Bad Request가 발생한다.
13.13 참고 자료 (References)
┌────┬──────────────────────────────────┬──────────────────────────────────────────────────────────────────┐
│ # │ 자료 │ URL │
├────┼──────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ 1 │ RFC 7239 - Forwarded HTTP │ https://www.rfc-editor.org/rfc/rfc7239 │
│ │ Extension │ │
├────┼──────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ 2 │ RFC 6648 - Deprecating "X-" │ https://www.rfc-editor.org/rfc/rfc6648 │
│ │ Prefix │ │
├────┼──────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ 3 │ RFC 7230 - HTTP/1.1 Message │ https://www.rfc-editor.org/rfc/rfc7230 │
│ │ Syntax │ │
├────┼──────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ 4 │ RFC 9110 - HTTP Semantics │ https://www.rfc-editor.org/rfc/rfc9110 │
├────┼──────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ 5 │ HAProxy PROXY Protocol Spec │ https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt │
├────┼──────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ 6 │ Nginx ngx_http_realip_module │ https://nginx.org/en/docs/http/ngx_http_realip_module.html │
├────┼──────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ 7 │ Nginx ngx_http_proxy_module │ https://nginx.org/en/docs/http/ngx_http_proxy_module.html │
├────┼──────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ 8 │ MDN - X-Forwarded-For │ https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/ │
│ │ │ Headers/X-Forwarded-For │
├────┼──────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ 9 │ Cloudflare HTTP Headers │ https://developers.cloudflare.com/fundamentals/reference/ │
│ │ │ http-headers/ │
├────┼──────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ 10 │ AWS ALB Headers │ https://docs.aws.amazon.com/elasticloadbalancing/latest/ │
│ │ │ application/x-forwarded-headers.html │
├────┼──────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ 11 │ Istio Gateway Network Topology │ https://istio.io/latest/docs/ops/configuration/ │
│ │ │ traffic-management/network-topologies/ │
├────┼──────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ 12 │ Spring Boot Forward Headers │ https://rwinch.github.io/spring-boot/howto/webserver/ │
│ │ │ use-behind-a-proxy-server.html │
└────┴──────────────────────────────────┴──────────────────────────────────────────────────────────────────┘