2026年11月12日
2026年11月12日
WordPress에 Cloudflare 설정하는 방법|네임서버 변경·SSL·실제 IP 복원·WAF
이 글은 일본어 원문을 현지화한 버전입니다. 일본어 원문 보기
WordPress에 Cloudflare를 적용하는 작업은 ① Cloudflare에 사이트를 추가하고 도메인의 네임서버를 변경, ② SSL/TLS 암호화 모드를 Full (strict)로 설정, ③ 방문자의 실제 IP를 서버에서 복원, ④ WAF 사용자 지정 규칙(Custom rules)으로 로그인 화면 보호, 이 순서로 진행합니다. 무료 플랜에서도 CDN·SSL·DDoS 대책·사용자 지정 규칙(5개까지)을 사용할 수 있습니다.
이 글에서는 각 설정과, 전환한 뒤 확인할 점을 설명합니다. Cloudflare 화면의 이름과 배치는 바뀔 수 있으므로, 여기서는 2026년 11월 시점의 공식 문서(developers.cloudflare.com)를 기준으로 작성했습니다.
작업 전에 정해 둘 것
- DNS 관리를 Cloudflare로 옮기게 됩니다. 도메인 등록처(레지스트라)는 바뀌지 않지만, DNS 레코드 편집은 Cloudflare 화면에서 하게 됩니다.
- 메일용 DNS 레코드(MX 등)는 프록시하지 않도록 설정합니다. Cloudflare를 거치는 것은 웹 통신(HTTP/HTTPS)뿐입니다.
- 오리진(WordPress를 설치한 서버)에 유효한 SSL 인증서가 있는지 확인합니다. Let's Encrypt 등으로 HTTPS를 사용하고 있다면 그대로 두면 됩니다.
- 레지스트라 쪽에서 DNSSEC를 활성화했다면 네임서버를 변경하기 전에 비활성화합니다. 오래된 DS 레코드가 남아 있으면 이름 확인(name resolution)에 실패합니다.
1단계: 사이트를 추가하고 네임서버 변경하기
- Cloudflare 계정을 만들고 대시보드에서 도메인을 추가합니다. 플랜은 Free를 선택할 수 있습니다.
- Cloudflare가 현재 DNS 레코드를 읽어 목록으로 보여 주므로 빠진 것이 없는지 확인합니다. 읽어 오지 못하는 레코드도 있으므로 현재 DNS 관리 화면과 비교해 부족한 것은 직접 추가합니다.
- 웹사이트의
A(또는AAAA·CNAME) 레코드는 프록시 상태를 켭니다(주황색 구름).MX나 메일 서버용A레코드는 끕니다(DNS only, 회색 구름). - Cloudflare가 2개의 네임서버 이름을 표시합니다. 이름은
〇〇.ns.cloudflare.com형태이며 계정마다 할당됩니다. 다른 글에 적힌 이름이 아니라 내 화면에 표시된 2개를 사용합니다. - 도메인 레지스트라의 관리 화면에서 네임서버를 그 2개로 변경합니다.
전환되었는지는 다음 명령으로 확인합니다. 반영에는 몇 시간이 걸릴 수 있습니다.
# 네임서버가 Cloudflare의 2개로 되어 있는지
dig NS example.com +short
# Cloudflare를 거치고 있는지(server: cloudflare와 cf-ray가 반환됨)
curl -sI https://example.com | grep -iE '^(server|cf-ray):'
Cloudflare 화면에서 도메인 상태가 활성(Active)이 되면 전환이 끝난 것입니다.
2단계: SSL/TLS를 Full (strict)로 설정하기
Cloudflare의 SSL/TLS에는 방문자와 Cloudflare 사이, Cloudflare와 오리진 사이를 어떻게 암호화할지 정하는 모드가 있습니다.
| 모드 | Cloudflare와 오리진 사이 | WordPress에서의 취급 |
|---|---|---|
| Off | 암호화하지 않음 | 사용하지 않음 |
| Flexible | HTTP(암호화하지 않음) | 사용하지 않음. 리디렉션 루프와 혼합 콘텐츠의 원인이 됨 |
| Full | HTTPS(인증서를 검증하지 않음) | 인증서를 준비할 때까지의 임시 방편 |
| Full (strict) | HTTPS(인증서를 검증함) | 이것으로 설정 |
설정은 Cloudflare 대시보드의 SSL/TLS 개요(Overview) 페이지에서 변경합니다. 기본값인 "Automatic SSL/TLS"는 오리진이 지원하는 범위에서 안전한 모드를 자동으로 선택하는 방식입니다. 직접 Full (strict)를 선택해 두면 의도하지 않은 모드로 동작하는 일이 없습니다. 오리진의 인증서는 공인 인증 기관의 것(Let's Encrypt 등)이나 Cloudflare가 발급하는 Origin CA 인증서를 사용할 수 있습니다.
Flexible을 피하는 이유는 다음과 같습니다. Flexible에서는 오리진에 HTTP로 도달하므로, WordPress나 서버가 "HTTP로 왔으니 HTTPS로 전환하라"고 응답하고, Cloudflare가 다시 HTTP로 오리진에 가져오는 일이 반복됩니다(브라우저에는 ERR_TOO_MANY_REDIRECTS라고 표시됩니다). Full (strict)라면 오리진에도 HTTPS로 도달하므로, WordPress 쪽에서 X-Forwarded-Proto를 보고 $_SERVER['HTTPS']를 덮어쓰는 추가 코드가 필요 없습니다.
아울러 WordPress의 "설정(Settings) → 일반(General)"에서 "WordPress 주소(URL)"와 "사이트 주소(URL)"가 https://로 되어 있는지 확인합니다. 이미지 등이 http://로 남아 있다면 혼합 콘텐츠 해결 방법을 참고하세요.
3단계: 방문자의 실제 IP 복원하기
Cloudflare를 거치면 서버에 도달하는 접속 출발지(REMOTE_ADDR)가 Cloudflare의 IP 주소가 됩니다. 이대로는 접속 로그, 댓글의 IP, 로그인 시도를 제한하는 플러그인 등이 모든 방문자를 Cloudflare의 IP로 취급합니다. Cloudflare는 방문자의 IP를 CF-Connecting-IP 헤더로 전달하므로, 접속 출발지가 Cloudflare일 때만 그 헤더를 신뢰하도록 설정합니다.
접속 출발지를 확인하지 않고 헤더 값으로 REMOTE_ADDR를 덮어쓰면, Cloudflare를 거치지 않고 오리진에 직접 접속한 사람이 헤더로 원하는 IP를 사칭할 수 있습니다. 오래된 글에 나오는 "HTTP_CF_CONNECTING_IP가 있으면 REMOTE_ADDR에 대입한다"는 코드만으로는 이런 이유로 사용하지 마세요.
Cloudflare의 IP 주소 목록은 https://www.cloudflare.com/ips/에 공개되어 있습니다. 목록은 바뀔 수 있으므로 정기적으로 다시 확인합니다.
Nginx의 경우
ngx_http_realip_module을 사용합니다. http 블록이나 server 블록에 Cloudflare의 범위를 모두 나열합니다.
# Cloudflare의 IP 범위(2026년 11월 시점. 최신 내용은 cloudflare.com/ips에서 확인)
set_real_ip_from 173.245.48.0/20;
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 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
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 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;
sudo nginx -t && sudo systemctl reload nginx
Apache의 경우(Debian / Ubuntu)
mod_remoteip를 사용합니다. /etc/apache2/conf-available/remoteip.conf를 만들고 Cloudflare의 범위를 RemoteIPTrustedProxy에 나열합니다.
RemoteIPHeader CF-Connecting-IP
RemoteIPTrustedProxy 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22
RemoteIPTrustedProxy 141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20
RemoteIPTrustedProxy 197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13
RemoteIPTrustedProxy 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22
RemoteIPTrustedProxy 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32
RemoteIPTrustedProxy 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32
sudo a2enmod remoteip
sudo a2enconf remoteip
sudo apache2ctl configtest
sudo systemctl restart apache2
접속 로그에 실제 IP를 출력하려면 /etc/apache2/apache2.conf의 LogFormat(combined)에서 %h를 %a로 바꿉니다.
서버 설정을 변경할 수 없는 경우(wp-config.php)
렌탈 서버 등에서 웹 서버 설정을 만질 수 없다면 wp-config.php에서 같은 판정을 합니다. require_once ABSPATH . 'wp-settings.php'; 줄보다 앞에 둡니다. 접속 출발지가 Cloudflare의 범위에 들어갈 때만 CF-Connecting-IP의 값(IP 주소로 올바른 것)을 사용합니다.
<?php
// Cloudflare를 거칠 때만 CF-Connecting-IP를 방문자의 IP로 사용
$cf_ranges = [
'173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22',
'141.101.64.0/18', '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20',
'197.234.240.0/22', '198.41.128.0/17', '162.158.0.0/15', '104.16.0.0/13',
'104.24.0.0/14', '172.64.0.0/13', '131.0.72.0/22',
'2400:cb00::/32', '2606:4700::/32', '2803:f800::/32', '2405:b500::/32',
'2405:8100::/32', '2a06:98c0::/29', '2c0f:f248::/32',
];
function cf_ip_in_range(string $ip, string $cidr): bool
{
[$net, $bits] = explode('/', $cidr);
$ip_bin = inet_pton($ip);
$net_bin = inet_pton($net);
if ($ip_bin === false || $net_bin === false || strlen($ip_bin) !== strlen($net_bin)) {
return false;
}
$bits = (int) $bits;
$bytes = intdiv($bits, 8);
if ($bytes > 0 && substr($ip_bin, 0, $bytes) !== substr($net_bin, 0, $bytes)) {
return false;
}
$rest = $bits % 8;
if ($rest === 0) {
return true;
}
$mask = (0xFF << (8 - $rest)) & 0xFF;
return (ord($ip_bin[$bytes]) & $mask) === (ord($net_bin[$bytes]) & $mask);
}
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP']) && !empty($_SERVER['REMOTE_ADDR'])) {
foreach ($cf_ranges as $range) {
if (cf_ip_in_range($_SERVER['REMOTE_ADDR'], $range)) {
$visitor = filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP);
if ($visitor !== false) {
$_SERVER['REMOTE_ADDR'] = $visitor;
}
break;
}
}
}
wp-config.php에는 처음부터 <?php가 있으므로 1행의 <?php는 옮겨 적지 말고 2행 이후를 붙여 넣습니다. 붙여 넣기 전에 wp-config.php의 백업을 만들어 두세요.
4단계: WAF 사용자 지정 규칙으로 로그인 화면 보호하기
Cloudflare WAF의 사용자 지정 규칙은 무료 플랜에서 5개까지 만들 수 있고, Log 이외의 동작(Block·Managed Challenge 등)을 선택할 수 있습니다. 생성은 대시보드의 Security rules 페이지에서 "Create rule"의 "Custom rules"를 선택하고, 조건(Field·Operator·Value)과 동작을 지정한 뒤 "Deploy"합니다.
예 1: 로그인 화면과 관리자 화면을 내 IP에만 열기
자택이나 회사의 고정 IP가 있는 경우의 예입니다. 식으로 쓰면 다음과 같습니다(203.0.113.10과 198.51.100.0/24는 자신의 IP로 바꿉니다).
(http.request.uri.path eq "/wp-login.php" or starts_with(http.request.uri.path, "/wp-admin/"))
and http.request.uri.path ne "/wp-admin/admin-ajax.php"
and not ip.src in {203.0.113.10 198.51.100.0/24}
동작은 Block으로 합니다. /wp-admin/admin-ajax.php는 문의 폼이나 무한 스크롤 같은 공개 페이지의 기능이 로그인하지 않은 상태에서 호출하는 경우가 있으므로 조건에서 제외했습니다.
예 2: 고정 IP가 없다면 국가와 Managed Challenge로 좁히기
(http.request.uri.path eq "/wp-login.php" or http.request.uri.path eq "/xmlrpc.php")
and not ip.src.country in {"JP"}
국가 판정에는 ip.src.country(ISO 3166-1의 2글자 코드)를 사용합니다. 오래된 글의 ip.geoip.country는 더 이상 권장되지 않습니다. 동작은 처음에는 Managed Challenge로 두고, 보안 이벤트에서 정상적인 접속이 걸리지 않는지 확인한 뒤 필요하면 Block으로 바꿉니다. Cloudflare 문서도 이 방식을 권장합니다. xmlrpc.php는 Jetpack이나 스마트폰 앱에서 글을 게시할 때 사용되므로, 사용하는지 확인한 뒤에 조건에 넣습니다.
국가에 의한 제한은 VPN 등으로 우회할 수 있는 보조책입니다. 2단계 인증이나 로그인 횟수 제한과 함께 사용하세요. Nginx 쪽에서 국가를 판정하는 방법은 Nginx GeoIP2로 국가별 접속을 제한하는 방법에서 설명합니다.
5단계: 캐시의 기본 동작과 게시 시 퍼지
Cloudflare는 기본적으로 HTML을 캐시하지 않습니다. 캐시하는 것은 이미지·CSS·JavaScript·폰트처럼 확장자로 정해지는 정적 파일입니다. 또한 Set-Cookie 헤더가 포함된 응답도 캐시하지 않습니다. 그래서 아무것도 설정하지 않으면 관리자 화면이나 로그인 화면이 Cloudflare에 캐시되는 일은 없습니다.
HTML까지 캐시하고 싶다면 Cache Rules로 대상을 넓히지만, 그때는 /wp-admin/, /wp-login.php, 미리보기, 로그인 중인 이용자의 화면을 대상에서 제외해야 합니다. 제외를 잘못하면 관리자용 화면이 다른 사람에게 표시될 우려가 있으므로, 자신이 없다면 기본값 그대로 두는 것이 무난합니다.
CSS나 이미지를 교체했는데 화면이 바뀌지 않을 때는 대시보드의 캐시 설정에서 퍼지(삭제)합니다. 글을 게시하거나 수정할 때마다 자동으로 퍼지하고 싶다면 Cloudflare의 API를 호출합니다. 먼저 Cloudflare에서 "Cache Purge" 권한만 가진 API 토큰을 만들고, 영역 ID(Zone ID, 도메인 개요 페이지에 표시됨)와 함께 wp-config.php에 상수로 적습니다.
define( 'CF_ZONE_ID', '영역 ID' );
define( 'CF_API_TOKEN', 'API 토큰' );
다음 코드를 자식 테마의 functions.php나 직접 만든 작은 플러그인(Code Snippets 등도 가능)에 넣습니다.
<?php
// 글을 게시·수정하면 해당 글과 메인 페이지의 캐시를 Cloudflare에서 삭제
function cf_purge_urls(array $urls): bool
{
if (!defined('CF_ZONE_ID') || !defined('CF_API_TOKEN')) {
return false;
}
$response = wp_remote_post(
'https://api.cloudflare.com/client/v4/zones/' . CF_ZONE_ID . '/purge_cache',
[
'headers' => [
'Authorization' => 'Bearer ' . CF_API_TOKEN,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode(['files' => array_values($urls)]),
'timeout' => 15,
]
);
if (is_wp_error($response)) {
error_log('Cloudflare purge failed: ' . $response->get_error_message());
return false;
}
$result = json_decode(wp_remote_retrieve_body($response), true);
return !empty($result['success']);
}
add_action('publish_post', function (int $post_id): void {
if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
return;
}
cf_purge_urls([get_permalink($post_id), home_url('/')]);
});
publish_post는 글이 게시되었을 때와 게시된 글을 수정했을 때 동작합니다. API의 purge_cache에는 URL을 지정하는 files 외에 전체를 지우는 purge_everything도 있지만, 전체 퍼지는 정적 파일도 모두 다시 가져와야 하므로 글을 게시할 때마다 사용하지는 않습니다.
전환한 뒤 확인할 것
curl -sI결과에server: cloudflare와cf-ray가 있고, 브라우저에 자물쇠 표시가 나타난다- 관리자 화면에 로그인할 수 있고, 글 저장과 미디어 업로드가 된다
- 새 댓글이나 접속 로그에 Cloudflare의 IP가 아니라 방문자의 IP가 기록된다
- 문의 폼에서 메일이 도착한다(
MX레코드가 프록시되지 않고 있다) - WAF 사용자 지정 규칙을 만들었다면, 허용하지 않은 회선(스마트폰의 모바일 회선 등)에서
wp-login.php를 열었을 때 차단이나 챌린지가 나타난다
자주 생기는 문제
ERR_TOO_MANY_REDIRECTS가 나타난다
SSL/TLS 모드가 Flexible로 되어 있지 않은지 확인합니다. 오리진에 인증서를 설치하고 Full (strict)로 바꾸면 해결됩니다.
관리자 화면에 들어갈 수 없게 되었다
WAF 사용자 지정 규칙의 조건에 내 IP나 국가가 포함되어 있지 않을 가능성이 있습니다. Cloudflare 대시보드에서 규칙을 일시적으로 비활성화하고, 보안 이벤트에서 어떤 규칙에 걸렸는지 확인한 뒤 식을 수정합니다.
모든 접속이 같은 IP로 보인다
3단계의 실제 IP 복원이 되어 있지 않습니다. Nginx라면 set_real_ip_from의 범위와 real_ip_header, Apache라면 remoteip가 활성화되어 있는지 확인합니다.
정리
- 네임서버는 내 계정에 할당된 2개로 변경하고,
MX등 메일용 레코드는 프록시하지 않는다 - SSL/TLS는 Full (strict). Flexible은 리디렉션 루프와 혼합 콘텐츠의 원인이 된다
- 실제 IP는 접속 출발지가 Cloudflare의 범위일 때만
CF-Connecting-IP를 신뢰해 복원한다 - 로그인 화면은 WAF 사용자 지정 규칙으로 IP나 국가를 좁히고, 처음에는 Managed Challenge로 상황을 지켜본다
- HTML은 기본적으로 캐시되지 않는다. 게시 시 퍼지는 "Cache Purge" 권한만 가진 API 토큰으로 한다
관련 글
참고: Restoring original visitor IPs(Cloudflare), Encryption modes(Cloudflare), Custom rules(Cloudflare WAF), Default cache behavior(Cloudflare)
WordPress에 대해 궁금한 점이 있으신가요? 이 사이트의 운영사는 일본에 있으며 일본어로 답변합니다. 문의하기(일본어)