2026年11月12日
2026年11月12日
How to Set Up Cloudflare for WordPress: Nameservers, SSL, Real IPs and WAF
This article is a localized edition of the Japanese original. Read the Japanese original
Setting up Cloudflare for WordPress takes four steps, in this order: (1) add your site to Cloudflare and switch the domain's nameservers, (2) set the SSL/TLS encryption mode to Full (strict), (3) restore the real visitor IP on your server, and (4) protect the login page with WAF custom rules. The Free plan already includes the CDN, SSL, DDoS protection and custom rules (up to 5).
This article walks through each setting and what to check after the switch. Cloudflare's screen names and layout change from time to time, so everything here follows Cloudflare's official documentation (developers.cloudflare.com) as of November 2026.
Decide these before you start
- DNS management moves to Cloudflare. Your registrar stays the same, but you will edit DNS records in the Cloudflare dashboard from now on.
- Do not proxy mail DNS records (MX and so on). Only web traffic (HTTP/HTTPS) goes through Cloudflare.
- Make sure your origin (the server running WordPress) has a valid SSL certificate. If HTTPS already works with Let's Encrypt or similar, you can keep it as is.
- If DNSSEC is enabled at your registrar, disable it before you switch nameservers. A leftover DS record will make name resolution fail.
Step 1: Add the site and switch nameservers
- Create a Cloudflare account and add your domain in the dashboard. You can choose the Free plan.
- Cloudflare scans your current DNS records and lists them. Check the list for gaps: some records cannot be detected, so compare it with your current DNS management screen and add anything missing by hand.
- Turn the proxy status on (orange cloud) for your website's
A(orAAAA/CNAME) records. Turn it off (DNS only, gray cloud) forMXrecords and forArecords used by the mail server. - Cloudflare shows two nameserver names. They look like
〇〇.ns.cloudflare.comand are assigned per account. Use the two shown in your own dashboard, not names you saw in another article. - In your domain registrar's control panel, change the nameservers to those two.
Use these commands to check whether the switch has taken effect. It can take several hours.
# Are the nameservers now the two Cloudflare ones?
dig NS example.com +short
# Is traffic going through Cloudflare? (server: cloudflare and cf-ray are returned)
curl -sI https://example.com | grep -iE '^(server|cf-ray):'
Once the domain shows as Active in the Cloudflare dashboard, the switch is complete.
Step 2: Set SSL/TLS to Full (strict)
Cloudflare's SSL/TLS settings have modes that decide how traffic is encrypted between visitors and Cloudflare, and between Cloudflare and your origin.
| Mode | Between Cloudflare and the origin | For WordPress |
|---|---|---|
| Off | Not encrypted | Do not use |
| Flexible | HTTP (not encrypted) | Do not use. Causes redirect loops and mixed content |
| Full | HTTPS (certificate not validated) | A stopgap until you have a certificate |
| Full (strict) | HTTPS (certificate validated) | Use this |
You change the mode on the SSL/TLS Overview page of the Cloudflare dashboard. The default, "Automatic SSL/TLS", picks the most secure mode your origin supports. Choosing Full (strict) yourself makes sure the site never runs in a mode you did not intend. For the origin certificate you can use one from a public certificate authority (such as Let's Encrypt) or a Cloudflare Origin CA certificate.
Here is why you should avoid Flexible. With Flexible, requests reach the origin over HTTP, so WordPress or the server answers "this came in over HTTP, redirect to HTTPS", and Cloudflare fetches from the origin over HTTP again, over and over (the browser shows ERR_TOO_MANY_REDIRECTS). With Full (strict), requests reach the origin over HTTPS too, so you do not need extra code in WordPress that reads X-Forwarded-Proto and rewrites $_SERVER['HTTPS'].
Also check that "WordPress Address (URL)" and "Site Address (URL)" under Settings → General (設定 → 一般 in the Japanese UI) both start with https://. If images and other assets still use http://, see how to fix mixed content.
Step 3: Restore the real visitor IP
Behind Cloudflare, the client address your server sees (REMOTE_ADDR) is a Cloudflare IP address. Left as is, your access logs, comment IPs and plugins that limit login attempts will treat every visitor as a Cloudflare IP. Cloudflare passes the visitor's IP in the CF-Connecting-IP header, so configure your server to trust that header only when the connection comes from Cloudflare.
If you overwrite REMOTE_ADDR with the header value without checking where the connection came from, anyone who bypasses Cloudflare and connects straight to your origin can claim any IP they like through that header. For this reason, do not use the code found in older articles that simply copies HTTP_CF_CONNECTING_IP into REMOTE_ADDR whenever it is present.
Cloudflare publishes its IP ranges at https://www.cloudflare.com/ips/. The list can change, so review it regularly.
Nginx
Use ngx_http_realip_module. List every Cloudflare range in the http or server block.
# Cloudflare IP ranges (as of November 2026; check cloudflare.com/ips for the latest)
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)
Use mod_remoteip. Create /etc/apache2/conf-available/remoteip.conf and list the Cloudflare ranges in 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
To log the real IP, change %h to %a in the LogFormat (combined) line of /etc/apache2/apache2.conf.
If you cannot change the server configuration (wp-config.php)
On shared hosting and other setups where you cannot touch the web server configuration, do the same check in wp-config.php. Place it before the require_once ABSPATH . 'wp-settings.php'; line. It uses the CF-Connecting-IP value (only if it is a valid IP address) only when the connecting address is inside a Cloudflare range.
<?php
// Use CF-Connecting-IP as the visitor IP only when the request comes through Cloudflare
$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 already starts with <?php, so do not copy the first <?php line; paste from the second line on. Back up wp-config.php before you paste.
Step 4: Protect the login page with WAF custom rules
On the Free plan you can create up to 5 WAF custom rules and choose any action except Log (Block, Managed Challenge and so on). To create one, open the Security rules page in the dashboard, choose "Create rule" → "Custom rules", set the condition (Field, Operator, Value) and the action, then click "Deploy".
Example 1: Open the login and admin pages only to your own IP
This example is for when your home or office has a fixed IP. Written as an expression it looks like this (replace 203.0.113.10 and 198.51.100.0/24 with your own IPs).
(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}
Set the action to Block. /wp-admin/admin-ajax.php is excluded from the condition because public-facing features such as contact forms and infinite scroll may call it for visitors who are not logged in.
Example 2: No fixed IP? Narrow by country with 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"}
Use ip.src.country (an ISO 3166-1 two-letter code) for the country. The ip.geoip.country field found in older articles is deprecated. Start with Managed Challenge as the action, check Security Events to make sure legitimate traffic is not being caught, then switch to Block if needed. Cloudflare's documentation recommends the same approach. xmlrpc.php is used when you post from Jetpack or the mobile apps, so confirm whether you use it before adding it to the condition.
Country restrictions are a supplementary measure that VPNs and similar tools can get around. Combine them with two-factor authentication and login attempt limits. For country checks on the Nginx side, see how to restrict access by country with Nginx GeoIP2.
Step 5: Default caching behavior and purging on publish
By default Cloudflare does not cache HTML. What it caches are static files identified by extension, such as images, CSS, JavaScript and fonts. It also does not cache responses that contain a Set-Cookie header. So with no extra configuration, your admin and login pages are never cached by Cloudflare.
If you want to cache HTML as well, you widen the scope with Cache Rules, but then you must exclude /wp-admin/, /wp-login.php, previews and pages shown to logged-in users. Getting the exclusions wrong can show admin-only screens to other people, so if you are not confident, leaving the defaults alone is the safer choice.
When you replace CSS or images and the change does not show up, purge the cache from the caching settings in the dashboard. To purge automatically every time a post is published or updated, call the Cloudflare API. First create an API token in Cloudflare that has only the "Cache Purge" permission, then define it as a constant in wp-config.php together with the zone ID (shown on the domain's Overview page).
define( 'CF_ZONE_ID', 'your-zone-id' );
define( 'CF_API_TOKEN', 'your-api-token' );
Put the following code in your child theme's functions.php or in a small custom plugin (Code Snippets also works).
<?php
// When a post is published or updated, purge that post and the home page from Cloudflare's cache
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 fires when a post is published and when an already published post is updated. Besides files, which takes a list of URLs, the purge_cache API also accepts purge_everything, which clears the whole zone. A full purge forces every static file to be fetched again, so do not use it every time you publish a post.
What to check after the switch
- The
curl -sIoutput includesserver: cloudflareandcf-ray, and the browser shows the padlock - You can log in to the admin, save posts and upload media
- New comments and access logs record the visitor's IP, not a Cloudflare IP
- Email from your contact form arrives (the
MXrecord is not proxied) - If you created WAF custom rules, open
wp-login.phpfrom a connection you did not allow (for example your phone's mobile data) and confirm you get a block or a challenge
Common problems
ERR_TOO_MANY_REDIRECTS
Check whether the SSL/TLS mode is set to Flexible. Install a certificate on the origin and switch to Full (strict) to fix it.
Locked out of the admin
Your own IP or country may not be included in the WAF custom rule's condition. Temporarily disable the rule from the Cloudflare dashboard, check Security Events to see which rule caught you, and then fix the expression.
Every visit shows the same IP
Real IP restoration from Step 3 is not working. On Nginx, check the set_real_ip_from ranges and real_ip_header; on Apache, check that remoteip is enabled.
Summary
- Switch to the two nameservers assigned to your account, and do not proxy
MXor other mail records - Use Full (strict) for SSL/TLS. Flexible causes redirect loops and mixed content
- Restore the real IP by trusting
CF-Connecting-IPonly when the connection comes from a Cloudflare range - Narrow the login page by IP or country with WAF custom rules, starting with Managed Challenge
- HTML is not cached by default. Purge on publish with an API token that has only the "Cache Purge" permission
Related articles
- How to set up Cloudflare DNS
- How to set up the Cloudflare WAF
- How to restrict access by country with Nginx GeoIP2
- How to fix mixed content
References: Restoring original visitor IPs (Cloudflare), Encryption modes (Cloudflare), Custom rules (Cloudflare WAF), Default cache behavior (Cloudflare)
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)