CDN이나 리버스 프록시 뒤의 원본 서버는 방문자가 아니라 프록시의 IP를 접속 주소로 봅니다. 방문자의 실제 IP는 CDN이 요청에 붙여 보내는 헤더에 들어 있습니다. 웹 서버는 신뢰하는 프록시에서 온 요청일 때만 그 헤더 값으로 접속 주소를 바꾸도록 설정해야 합니다. 작업은 구간 파악 → 기준 헤더 결정 → 신뢰 범위 설정 → 로그 검증 → 위조 차단 순으로 진행합니다.
CDN·프록시 뒤에서 실제 IP는 왜 바뀌어 보이나요?
서버가 기록하는 접속 주소는 TCP 연결을 맺은 상대의 주소입니다. CDN을 거치면 연결 상대는 CDN 서버가 되고, 방문자 주소는 헤더로 옮겨집니다. Cloudflare 문서도 리버스 프록시 구조에서는 원본 로그에 기본적으로 Cloudflare IP가 기록된다고 설명합니다.
방문자 주소가 담기는 헤더는 서비스마다 다릅니다. 공식 문서에서 확인한 내용은 다음과 같습니다.
| 구분 | 실제 IP가 담기는 곳 | 문서가 밝힌 주의점 |
|---|---|---|
| Cloudflare | CF-Connecting-IP 헤더. Pseudo IPv4(가상 IPv4) 설정을 「헤더 덮어쓰기(Overwrite Headers)」로 두면 실제 IPv6 주소는 CF-Connecting-IPv6에 담깁니다. | 헤더가 원본에 오지 않으면 Transform Rules와 Managed Transforms를 확인합니다. IP 목록은 정기적으로 갱신해야 합니다. |
| Amazon CloudFront | X-Forwarded-For 끝에 연결 주소를 덧붙입니다. CloudFront-Viewer-Address 헤더는 「주소:포트」 형식입니다. | CloudFront-Viewer-Address는 원본 요청 정책에 추가해야 전달됩니다. 방문자가 보낸 X-Real-IP는 제거됩니다. |
| 일반 프록시·로드밸런서 | X-Forwarded-For(사실상의 표준 헤더). 표준 후속 헤더로 Forwarded가 있습니다. | 목록의 왼쪽 값은 방문자가 임의로 넣을 수 있습니다. 같은 헤더가 여러 개 오면 하나의 목록으로 합쳐 읽습니다. |
구간이 둘 이상이면 주소가 하나 더 붙습니다. AWS 문서는 CloudFront 뒤에 로드밸런서가 있으면 엣지 서버 주소까지 덧붙어 「192.0.2.2,192.0.2.199」처럼 원본에 도착한다고 예시합니다. 어느 위치의 값이 방문자인지는 구간 구성을 알아야 판단할 수 있습니다.
설정 전에 무엇을 조사해야 하나요?
1단계: 방문자부터 원본까지의 구간을 적습니다
예를 들어 가상의 ○○ 기업이 「방문자 → CDN → 로드밸런서 → nginx → 애플리케이션」 구조라면 구간은 셋입니다. 구간마다 어떤 헤더를 추가하거나 덮어쓰는지, IP 범위를 어디에서 받는지를 적습니다. CDN 범위는 각 서비스가 공개하는 IP 목록을 기준으로 삼습니다.
완료 기준: 구간별 「헤더 처리 방식」과 「IP 범위 출처」가 한 표에 모두 채워졌습니다.
2단계: 기준 헤더와 읽는 위치를 정합니다
CF-Connecting-IP처럼 값이 하나뿐인 전용 헤더는 해석이 단순합니다. X-Forwarded-For는 목록이므로 읽는 위치를 정해야 합니다. MDN 문서는 맨 왼쪽이 방문자, 맨 오른쪽이 가장 최근 프록시라고 설명합니다.
MDN 문서는 보안 용도에 쓸 주소를 고르는 방법으로 두 가지를 제시합니다.
- 프록시 개수 방식: 인터넷과 서버 사이의 프록시 수를 정하고, 오른쪽 끝에서 「개수 - 1」만큼 왼쪽으로 이동한 값을 씁니다.
- 신뢰 목록 방식: 신뢰하는 프록시 범위를 정하고, 오른쪽부터 읽으며 목록에 있는 주소를 건너뜁니다. 처음 만나는 목록 밖 주소를 씁니다.
같은 문서는 맨 왼쪽 값을 접근 제어나 요청 제한 같은 보안 용도에 쓰지 말라고 설명합니다. 실무에서는 CDN 범위가 바뀌어도 대응하기 쉬운 신뢰 목록 방식을 권합니다.
완료 기준: 시험 요청 한 건의 헤더 원문을 확보했고, 몇 번째 값이 방문자인지 구간표에 기록했습니다.
웹 서버에서는 실제 IP를 어떻게 복원하나요?
3단계-1: nginx는 realip 모듈을 씁니다
nginx 문서는 이 모듈이 기본 빌드에 포함되지 않으며, 빌드할 때 --with-http_realip_module 옵션으로 켠다고 설명합니다. 먼저 사용 중인 nginx에 모듈이 들어 있는지 확인합니다. 설정 항목은 세 가지입니다.
- set_real_ip_from: 신뢰하는 프록시 주소나 범위를 한 줄에 하나씩 적습니다. CDN 범위와 로드밸런서 범위를 모두 넣습니다.
- real_ip_header: 방문자 주소를 읽을 헤더를 지정합니다. 기본값이 X-Real-IP이므로 CF-Connecting-IP나 X-Forwarded-For를 명시합니다.
- real_ip_recursive: X-Forwarded-For를 쓸 때 on으로 둡니다.
nginx 문서는 real_ip_recursive가 off면 헤더의 마지막 주소로, on이면 마지막 비신뢰 주소로 바꾼다고 설명합니다. 예를 들어 헤더가 「203.0.113.7, 192.168.2.1」이고 192.168.2.1이 신뢰 범위라면 결과가 갈립니다. off일 때는 마지막 값인 192.168.2.1, 즉 프록시 자신이 방문자로 기록됩니다. on일 때는 신뢰 범위를 건너뛰고 203.0.113.7을 고릅니다.
3단계-2: Apache는 mod_remoteip를 씁니다
Cloudflare 문서는 mod_cloudflare를 더 이상 업데이트·지원하지 않는다며 mod_remoteip를 권합니다. RemoteIPHeader로 헤더를 지정하고, RemoteIPTrustedProxy로 신뢰 프록시를 적습니다.
Apache 문서는 두 신뢰 지시어의 차이를 설명합니다. RemoteIPTrustedProxy로 등록한 프록시가 전달한 사설 주소는 방문자 주소로 인정하지 않습니다. RemoteIPInternalProxy는 사설 주소까지 인정합니다. 사내망 프록시가 섞인 구성이라면 이 구분을 먼저 정합니다.
같은 문서는 신뢰 지시어 없이 RemoteIPHeader만 두면 헤더를 보낸 모든 호스트를 신뢰한다고 경고합니다. 이 경우 누구든 주소를 위조해 IP 기반 접근 제한을 통과할 수 있습니다. 범위가 많으면 RemoteIPTrustedProxyList로 별도 파일에서 읽게 합니다.
완료 기준: 설정 문법 검사를 통과해 반영했고, 로그의 방문자 주소 칸에 CDN 범위 주소가 더 이상 나오지 않습니다.
복원이 맞는지는 어떻게 검증하나요?
4단계: 로그에 두 주소를 함께 기록합니다
복원된 방문자 주소만 남기면 오류가 생겼을 때 원인을 추적하기 어렵습니다. nginx 문서는 원래 연결 주소를 $realip_remote_addr 변수에 보존한다고 설명합니다. Apache 문서는 %a가 복원된 주소, %{c}a가 실제 연결 주소라고 설명합니다. Cloudflare 문서도 Apache 로그 형식의 %h를 %a로 바꾸라고 안내합니다.
CloudFront를 쓴다면 User-Agent도 확인합니다. AWS 문서는 User-Agent 기준 캐시를 설정하지 않으면 원본에 「Amazon CloudFront」로 바꿔 보낸다고 설명합니다. 이 상태로는 주소를 복원해도 어떤 봇의 요청인지 구분할 수 없습니다. 배포 설정에서 User-Agent가 원본까지 전달되는지 함께 점검합니다.
복원된 주소는 크롤러 검증에도 쓰입니다. Google 문서는 로그의 접속 IP로 역방향 DNS를 조회하고, 정방향 조회 결과가 같은지 확인하라고 안내합니다. 공개 IP 범위 파일과 대조하는 방법도 제시합니다. 로그에 CDN 주소가 남아 있으면 두 방법 모두 쓸 수 없습니다.
완료 기준: 같은 요청 한 줄에 방문자 주소, 연결 주소, User-Agent가 모두 기록됩니다.
5단계: 위조와 우회를 시험합니다
| 시험 | 방법 | 통과 기준 |
|---|---|---|
| 위조 헤더 시험 | CDN을 거치는 요청에 X-Forwarded-For 임의 값을 넣어 보냅니다. | 로그에 임의 값이 아니라 시험 장비의 실제 공인 IP가 기록됩니다. |
| 원본 직접 접근 시험 | 원본 서버 IP로 직접 접속하며 헤더에 임의 주소를 넣습니다. | 연결이 거부되거나, 헤더가 무시되고 연결 주소가 기록됩니다. |
| 다중 구간 시험 | 로드밸런서를 거치는 경로로 요청합니다. | 로그에 로드밸런서 주소가 방문자로 기록되지 않습니다. |
| IPv6 시험 | IPv6 회선에서 접속합니다. | IPv6 주소, 또는 의도한 가상 IPv4 주소가 기록됩니다. |
원본 직접 접근 시험을 통과하지 못하면 다른 설정도 보안 효과가 없습니다. MDN 문서는 원본 서버가 인터넷에서 직접 접근 가능한 경우를 경고합니다. 이때는 신뢰 프록시가 있어도 X-Forwarded-For의 어떤 값도 보안 용도로 믿을 수 없다고 설명합니다. Cloudflare 문서도 Cloudflare IP에서 오는 트래픽만 받도록 방화벽 등에서 제한하라고 권합니다.
운영 중에는 무엇을 계속 관리해야 하나요?
신뢰 범위는 한 번 넣고 끝나는 설정이 아닙니다. Cloudflare 문서는 IP 목록을 정기적으로 갱신해야 한다고 밝힙니다. 실무에서는 범위를 별도 파일로 분리하고 갱신 주기와 담당자를 정하는 방법을 권합니다. CDN 교체나 로드밸런서 추가처럼 구간이 바뀌면 1단계부터 다시 확인합니다.
IP 주소는 개인정보에 해당할 수 있습니다. MDN 문서도 이 헤더가 민감한 정보를 노출한다고 설명합니다. 로그 보관 기간과 열람 권한은 사내 개인정보 기준에 맞춥니다.
서버 담당자에게는 다음 문안을 그대로 보낼 수 있습니다.
- 방문자부터 원본 서버까지 거치는 구간(CDN, 로드밸런서, 웹 서버)과 구간별 IP 범위 출처를 알려 주십시오.
- 웹 서버가 방문자 주소를 어느 헤더의 몇 번째 값에서 읽는지, 신뢰 프록시 범위가 어떻게 지정되어 있는지 공유해 주십시오.
- 접속 로그에 복원된 방문자 주소, 원래 연결 주소, User-Agent가 함께 기록되는지 확인해 주십시오.
- 원본 서버 IP로 직접 접속할 때 차단되는지, 위조 헤더가 무시되는지 시험 결과를 알려 주십시오.
점검 결과는 다음 항목으로 기록합니다.
| 항목 | 기록할 내용 |
|---|---|
| 점검일·점검자 | 날짜와 담당자 이름 |
| 구간 구성 | 방문자부터 애플리케이션까지의 구간 순서 |
| 기준 헤더·읽는 위치 | 헤더 이름, 신뢰 목록 방식 또는 프록시 개수 방식 |
| 신뢰 범위 갱신일 | CDN IP 목록을 마지막으로 반영한 날짜 |
| 시험 결과 | 위조 헤더·직접 접근·다중 구간·IPv6 시험별 통과 여부 |
정리
- 방문자부터 원본까지의 구간과 구간별 IP 범위 출처를 한 표로 정리합니다.
- 기준 헤더를 정하고, X-Forwarded-For라면 오른쪽부터 신뢰 범위를 건너뛰는 방식으로 읽습니다.
- nginx는 realip 모듈, Apache는 mod_remoteip에 신뢰 프록시 범위를 함께 지정합니다.
- 로그에 복원 주소, 연결 주소, User-Agent를 함께 남기고 위조 헤더 시험과 원본 직접 접근 시험을 실행합니다.
- CDN IP 목록 갱신 주기와 담당자를 정하고, 구간이 바뀔 때마다 같은 시험을 다시 실행합니다.
참고 자료
- Module ngx_http_realip_module (nginx, 확인일 2026-10-20)
- Apache Module mod_remoteip (Apache HTTP Server Project, 확인일 2026-10-20)
- X-Forwarded-For header (MDN Web Docs, 확인일 2026-10-20)
- Restoring original visitor IPs (Cloudflare Docs, 확인일 2026-10-20)
- Add CloudFront request headers (Amazon CloudFront Developer Guide, 확인일 2026-10-20)
- Request and response behavior for custom origins (Amazon CloudFront Developer Guide, 확인일 2026-10-20)
- Verifying Googlebot and other Google crawlers (Google Search Central, 확인일 2026-10-20)
