클라이언트 앞에 서서 요청을 받아 뒤쪽 서버에 대신 전달하는 중개 서버.
포워드 프록시와의 차이 — 누구를 대신하는가
포워드 프록시 (클라이언트 대신)
- [클라이언트] → [프록시] → 인터넷 → [서버]
- 서버는 프록시의 IP만 본다. 클라이언트를 숨긴다
- 예: 사내 프록시, VPN
리버스 프록시 (서버 대신)
- [클라이언트] → 인터넷 → [프록시] → [서버들]
- 클라이언트는 프록시만 안다. 서버 구성을 숨긴다
- 예: Nginx, ALB, CloudFront
"누구를 숨기는가" 로 구분하면 헷갈리지 않는다.
무엇을 하나
| 역할 | 내용 |
|---|---|
| 로드 밸런싱 | 여러 서버에 요청 분배 |
| SSL 종료 | 여기서 HTTPS를 풀고 뒤로는 HTTP로 (애플리케이션 부담 감소) |
| 캐싱 | 정적 파일·응답을 대신 보관 |
| 압축 | gzip·brotli 처리 |
| 보안 | 내부 IP·포트·기술 스택을 숨김, WAF, DDoS 완화 |
| 라우팅 | 경로별로 다른 서비스에 (/api → 백엔드, / → 프론트) |
| 무중단 배포 | 트래픽을 새 버전으로 점진 전환 |
로드 밸런싱 알고리즘
- 라운드 로빈 — : 순서대로 (가장 단순)
- 가중 라운드 로빈 — : 서버 성능에 비례
- 최소 연결 — : 현재 연결이 가장 적은 곳 → 요청 처리 시간이 들쭉날쭉할 때 유리
- IP 해시 — : 같은 클라이언트는 항상 같은 서버 (세션 고정)
- 최소 응답시간 — : 가장 빠른 서버
세션 고정(sticky session)은 최후의 수단이다. 그 서버가 죽으면 세션이 날아가고, 스케일 아웃 효과도 떨어진다. 세션을 Redis 같은 외부 저장소로 빼는 것이 정석이다.
헬스 체크
죽은 서버로 요청을 보내지 않으려면 상태를 계속 확인해야 한다.
- liveness — : 프로세스가 살아 있나 → 죽었으면 재시작 readiness : 요청을 받을 준비가 됐나 → 아니면 로드밸런서에서 제외
둘을 구분하는 것이 중요하다. 기동 중이라 아직 준비가 안 된 서버는 "죽은" 것이 아니므로 재시작하면 안 되고, 트래픽만 안 보내면 된다.
원본 클라이언트 IP를 잃는 문제 — 매우 흔한 실무 이슈
프록시를 거치면 애플리케이션은 프록시의 IP만 본다.
X-Forwarded-For: 실제클라이언트IP, 프록시1IP, 프록시2IP
X-Forwarded-Proto: https
X-Forwarded-Host: example.com
이 헤더를 신뢰하도록 설정해야 한다.
server.forward-headers-strategy: native # Spring Boot
보안 주의 — 이 헤더는 클라이언트가 위조할 수 있다. 신뢰할 수 있는 프록시를 거친 경우에만 사용해야 하며, 그렇지 않으면 IP 기반 접근 제어나 rate limit이 뚫린다.
SSL 종료 후 리디렉션 루프 — 자주 겪는 함정
프록시가 HTTPS를 풀고 HTTP로 전달
→ 애플리케이션이 "HTTP네? HTTPS로 리디렉션!" 이라고 응답
→ 다시 프록시를 거쳐 HTTP로 도착
→ 또 리디렉션 … ⟳ 무한 루프
X-Forwarded-Proto를 보도록 설정하면 해결된다.
API 게이트웨이와의 차이
리버스 프록시가 네트워크 계층의 중개라면, API 게이트웨이는 여기에 인증·인가, rate limiting, 요청 변환, 집계, 과금까지 얹은 것이다. 경계가 뚜렷하지 않고 Nginx에 모듈을 붙여 게이트웨이처럼 쓰기도 한다.