포워드 / 리버스 프록시
웹사이트에 접속할 때 클라이언트가 항상 서버와 직접 연결되는 것은 아닙니다. 중간에 요청을 대신 받아 다른 쪽으로 전달하고 응답을 돌려주는 서버가 끼어 있는 경우가 많고, 이 중계 서버를 프록시(proxy)라고 합니다. 프록시는 요청과 응답을 중간에서 보고, 걸러 내고, 저장하고, 바꿀 수 있는 위치에 있어서 보안, 성능, 관리 면에서 여러 가지 일을 맡습니다. 프록시는 어느 편을 대신하느냐에 따라 크게 두 가지로 나뉩니다.
포워드 프록시(forward proxy)는 클라이언트 쪽에 놓여 클라이언트를 대신해 외부 서버에 요청을 보냅니다. 회사 사내망에서 인터넷으로 나가는 요청을 한곳으로 모아 통제하는 구성이 대표적입니다. 리버스 프록시(reverse proxy)는 서버 쪽에 놓여 서버를 대신해 클라이언트의 요청을 받습니다. 클라이언트에게는 프록시가 곧 서버로 보이고, 요청을 실제로 처리하는 백엔드 서버는 그 뒤에 숨어 있습니다. 이 토픽에서는 포워드 프록시의 중계와 캐시, 리버스 프록시의 분산과 TLS 종료, 두 프록시의 차이를 단계별로 봅니다.
핵심 개념
프록시
요청과 응답을 중간에서 대신 전달하는 서버입니다. 양쪽 모두 프록시와 연결을 맺고, 프록시가 반대편과 따로 연결을 맺어 메시지를 이어 줍니다.
핵심 개념
포워드 프록시
클라이언트의 대리인입니다. 보통 클라이언트가 프록시를 지정해 요청을 보내면, 프록시가 외부 서버에 대신 요청합니다. 접속 제어, 캐시, 사용 기록에 쓰입니다.
핵심 개념
리버스 프록시
서버의 대리인입니다. 클라이언트는 프록시 주소로만 접속하고, 프록시가 내부의 백엔드 서버로 요청을 전달합니다. nginx와 HAProxy가 대표적인 리버스 프록시 소프트웨어입니다.
핵심 개념
TLS 종료
리버스 프록시가 HTTPS 연결을 대신 끝맺고 요청을 복호화하는 방식입니다. 인증서를 프록시에만 두면 되어 관리가 쉽고, 백엔드는 암호화 부담을 덜 수 있습니다. 프록시와 백엔드 사이가 평문이 되므로 사설망 안에서 쓰는 것이 전제입니다.
핵심 개념
캐싱
프록시가 응답을 저장해 두었다가, 같은 요청이 오면 저장본이 아직 유효할 때 원래 서버에 묻지 않고 저장본으로 답합니다. 두 종류의 프록시 모두 응답 속도를 높이고 서버 부하를 줄이는 데 씁니다.
핵심 개념
원본 주소 전달 헤더
프록시를 거치면 백엔드에는 프록시의 주소만 보입니다. 원래 클라이언트 주소는 X-Forwarded-For나 표준 Forwarded 헤더에 담아 전달하는 것이 일반적입니다.
프록시의 배경
HTTP 명세(RFC 9110)는 프록시를 클라이언트가 보통 설정으로 골라 쓰는 메시지 중계자로, 게이트웨이(리버스 프록시)를 바깥쪽에서는 원래 서버처럼 행동하는 중계자로 구분합니다. RFC 9110은 프록시를 조직의 HTTP 요청을 한곳으로 모아 보안과 공유 캐시를 맡기는 데 쓴다고, 게이트웨이를 서버 성능 개선과 여러 서버로의 분산에 쓴다고 설명합니다. 많은 웹 서비스가 앞단에 리버스 프록시나 로드 밸런서를 두고, CDN도 넓게 보면 전 세계에 분산된 리버스 프록시입니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
포워드 프록시와 리버스 프록시의 차이는?
포워드 프록시는 클라이언트를 대신해 외부 서버에 요청을 보내고, 리버스 프록시는 서버를 대신해 클라이언트의 요청을 받습니다. 앞은 보통 클라이언트가 프록시를 지정하고(관리자가 가로채는 투명 프록시도 있습니다), 뒤는 클라이언트가 존재를 몰라도 됩니다.
면접 질문
리버스 프록시를 쓰면 무엇이 좋나요?
부하 분산, TLS 종료, 정적 응답 캐시, 내부 서버 구조의 은닉 같은 이점이 있습니다. 서버를 추가하거나 교체해도 클라이언트는 같은 주소로 접속합니다.
면접 질문
프록시 뒤의 서버는 클라이언트 IP를 어떻게 알죠?
프록시가 요청을 새로 보내므로 서버에는 프록시의 주소가 출발지로 보입니다. 그래서 프록시가 X-Forwarded-For나 Forwarded 헤더에 원래 주소를 담아 전달하고, 서버는 신뢰하는 프록시가 넣은 값만 사용합니다.
면접 질문
프록시와 VPN은 어떻게 다른가요?
프록시는 보통 특정 응용(HTTP 등)의 요청을 대신 전달하고, IPsec 같은 VPN은 IP 계층에서 트래픽을 암호화된 터널로 보냅니다(SSL VPN처럼 다른 방식도 있습니다). 프록시는 보통 그 자체로 구간을 암호화하지는 않습니다.
실제 시스템에서는
함께 보면 좋은 토픽
참고 자료
Squid — 포워드 프록시 설정과 사용
# squid.conf: 사내망(10.0.0.0/8)만 프록시를 쓰게 한다 http_port 3128 acl localnet src 10.0.0.0/8 acl blocked dstdomain .blocked.example http_access deny blocked # 차단 목록을 먼저 검사 http_access allow localnet http_access deny all # 나머지는 모두 거부 # 클라이언트에서 프록시를 거쳐 요청하기 curl -x http://10.0.0.1:3128 http://example.com/ -I
Squid는 대표적인 포워드 프록시 소프트웨어로, http_port로 프록시가 요청을 받을 포트를 정합니다. http_access 규칙은 위에서부터 차례로 검사해 처음 일치한 규칙이 적용되므로, 차단 목록(blocked)을 허용 규칙보다 먼저 두어야 차단이 먹힙니다. 마지막 줄의 deny all은 앞의 규칙에 일치하지 않은 요청을 모두 거부하는 규칙이며, 이 줄이 없으면 Squid는 마지막 규칙의 반대 동작을 기본으로 삼아 의도와 다르게 동작할 수 있습니다. 시각화의 포워드 프록시 탭에서 차단 도메인 요청이 프록시에서 거절되는 것과 같은 동작입니다. curl의 -x 옵션은 요청을 지정한 프록시로 보내며, 이때 요청 줄에는 전체 URL이 담겨 프록시가 어느 서버로 전달할지 알 수 있습니다.
nginx — 리버스 프록시와 로드 밸런싱
upstream backend {
server 10.0.1.11:8080;
server 10.0.1.12:8080;
}
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/ssl/example.crt;
ssl_certificate_key /etc/ssl/example.key;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
upstream 블록은 요청을 나눠 보낼 백엔드 서버 묶음이고, 별도 설정이 없으면 nginx는 요청을 서버에 번갈아 분배하는 라운드 로빈을 씁니다. server 블록은 443 포트에서 인증서로 HTTPS 연결을 끝맺고(TLS 종료), proxy_pass로 요청을 백엔드에 HTTP로 넘깁니다. 프록시를 거치면 백엔드는 모든 요청이 프록시에서 온 것으로 보게 됩니다. 그래서 proxy_set_header로 원래의 Host와, 클라이언트 주소를 담는 X-Forwarded-For, 원래의 접속 방식(http 또는 https)을 담는 X-Forwarded-Proto 헤더를 넘겨 줍니다. X-Forwarded-For는 표준 헤더가 아니라 널리 쓰이는 관례이므로, 백엔드는 신뢰할 수 있는 프록시가 넣은 값만 믿어야 합니다.
Python — 라운드 로빈 분배와 원본 주소 전달
from itertools import cycle BACKENDS = cycle(["10.0.1.11:8080", "10.0.1.12:8080"]) def route(client_ip, headers): backend = next(BACKENDS) # 서버를 번갈아 고른다 out = dict(headers) out["X-Forwarded-For"] = client_ip # 원래 클라이언트 주소를 전달 return backend, out for i in range(3): print(route("198.51.100.7", {"Host": "www.example.com"})) # 10.0.1.11, 10.0.1.12, 10.0.1.11 순서로 선택
itertools.cycle은 목록을 끝없이 되풀이하는 반복자여서, next를 부를 때마다 백엔드 A, B, A 순서로 고르는 라운드 로빈을 한 줄로 흉내 냅니다. 시각화의 리버스 프록시 탭에서 요청 1이 A로, 요청 2가 B로 가는 것과 같은 동작입니다. route 함수는 선택한 백엔드와 함께 원래 클라이언트 주소를 X-Forwarded-For 헤더에 담아 돌려줍니다. 실제 프록시에는 이미 들어 있는 X-Forwarded-For에 주소를 덧붙이는 처리가 필요합니다. 실제 프록시는 서버의 상태 검사, 연결 재사용, 실패 시 재시도, 가중치를 함께 다루므로 이 코드는 분배 원리만 보이는 최소 예시입니다.
Q1
포워드 프록시(Forward Proxy)의 역할은?
Q2
리버스 프록시(Reverse Proxy)의 역할은?
Q3
리버스 프록시가 제공할 수 있는 기능의 예로 알맞은 것은?