2026年11月14日
2026年11月14日
Set Up Cloudflare CDN for WordPress: SSL Mode, Caching and Real IP
This article is a localized edition of the Japanese original. Read the Japanese original
Cloudflare is a service that routes traffic to your site through its edge servers once you point your domain's nameservers to Cloudflare. Even the Free plan gives you a CDN (caching of static files), HTTPS, and DDoS protection.
This article walks through putting an existing WordPress site behind Cloudflare, plus the settings that most often cause trouble with WordPress: the SSL mode, HTML caching, and visitor IP addresses. Cloudflare's behavior and screens were checked against the official documentation as of November 2026. Screen labels are given in English. Because the dashboard menus change from time to time, search for the setting name in the dashboard's search box if you can't find it.
What to know before you start
- By default, HTML is not cached. Cloudflare decides by file extension and caches CSS, JS, images, fonts, PDFs and similar files. Pages that WordPress generates (HTML) are fetched from the origin (your own server) on every request unless you configure otherwise.
- Nothing is cached when the origin returns
Cache-Control: private,no-store,no-cacheormax-age=0, when it returnsSet-Cookie, or when the request method is not GET. - So if all you want is static file delivery and HTTPS, you barely need to change anything on the WordPress side. If you also want HTML cached, you must set up the exclusion conditions described later correctly.
Step 1: Add the site and switch nameservers
- Create a Cloudflare account and add your domain in the dashboard.
- Choose a plan. Everything in this article works on the Free plan.
- Your existing DNS records are imported. Compare them with your current DNS settings: check that the A records (for example
example.comandwww) point to your current server's IP and that the MX records for email are not missing. - Set the proxy status of the A/CNAME records for the website to "Proxied" (orange cloud). Set hostnames used for anything other than the web, such as a mail server, to "DNS only".
- Enter the two nameserver names shown on screen in the control panel of the registrar where your domain is registered.
Check whether the switch has happened with this command.
dig NS example.com +short
If it returns the nameserver names Cloudflare assigned to you, the switch is complete. How long it takes depends on your registrar and on DNS caching at the parent zone.
For more detailed DNS settings, see also Cloudflare DNS settings.
Step 2: Choose the SSL/TLS encryption mode
There are two legs to SSL/TLS: between the visitor and Cloudflare, and between Cloudflare and the origin. The encryption mode decides how the second leg works.
| Mode | Connection from Cloudflare to the origin | When to use it with WordPress |
|---|---|---|
| Off | HTTP (no encryption) | Don't use it |
| Flexible | Always HTTP | Only when the origin has no certificate. Prone to redirect loops |
| Full | Same protocol as the visitor. Over HTTPS, the certificate is not validated | When you only have a self-signed certificate |
| Full (strict) | Same protocol as the visitor. Over HTTPS, the certificate is validated | Recommended. Requires a public certificate or a Cloudflare Origin CA certificate |
| Strict (SSL-Only Origin Pull) | Always HTTPS, with certificate validation | When you want the origin to accept HTTPS only |
As of November 2026, the default is "Automatic SSL/TLS", which picks the most secure mode your origin supports (it is being rolled out gradually, so some zones only show the manual options). If you choose manually, install a valid certificate on the origin and select Full (strict). The origin certificate can be from Let's Encrypt or a Cloudflare-issued Origin CA certificate (Setting up a Cloudflare Origin CA certificate).
Why Flexible causes redirect loops
In Flexible mode, Cloudflare connects to the origin over HTTP even when the visitor arrives over HTTPS. If the WordPress site address is set to https://, or .htaccess or similar redirects HTTP to HTTPS, the origin answers with a redirect to HTTPS, Cloudflare connects over HTTP again, and the back-and-forth ends in ERR_TOO_MANY_REDIRECTS. The real fix is to install a certificate on the origin and switch to Full (strict).
Redirecting HTTP to HTTPS
You can redirect to HTTPS with Cloudflare's Always Use HTTPS (an Edge Certificates setting). It is available on every plan. If your origin also redirects, there is no loop as long as you use Full (strict).
Do you need a wp-config.php setting?
With Full or Full (strict), when a visitor arrives over HTTPS, Cloudflare also connects to the origin over HTTPS, so WordPress sees the request as HTTPS. You do not need code that rewrites $_SERVER['HTTPS'].
Code that reads X-Forwarded-Proto and sets $_SERVER['HTTPS'] to on is for setups where the origin only accepts HTTP (Flexible, or a load balancer in front that terminates TLS). Clients can set this header freely, so do not trust it unless connections to the origin are restricted to Cloudflare (or the load balancer).
Step 3: Restore visitor IP addresses
Behind Cloudflare, the connecting address the origin sees (REMOTE_ADDR) is a Cloudflare IP. WordPress uses REMOTE_ADDR for things like the commenter's IP, and many plugins that limit login attempts look at the same value. So restore the real visitor IP on the web server.
Cloudflare sends the visitor's IP in the CF-Connecting-IP header. Anyone can forge this header, though, so configure the server to accept it only on connections coming from Cloudflare IPs. Cloudflare publishes its IP ranges at https://www.cloudflare.com/ips/ (they can change, so check them periodically).
Apache (mod_remoteip, example for Ubuntu/Debian):
sudo a2enmod remoteip
sudo nano /etc/apache2/conf-available/remoteip.conf
RemoteIPHeader CF-Connecting-IP
# List every range from https://www.cloudflare.com/ips/, one per line
RemoteIPTrustedProxy 173.245.48.0/20
RemoteIPTrustedProxy 103.21.244.0/22
# (continue with all published IPv4 and IPv6 ranges)
sudo a2enconf remoteip
sudo apache2ctl configtest
sudo systemctl restart apache2
To log the visitor IP in the access log as well, change %h to %a in LogFormat.
Nginx (ngx_http_realip_module):
# Put this in the http block or a server block
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
# (continue with all published ranges)
real_ip_header CF-Connecting-IP;
sudo nginx -t
sudo systemctl reload nginx
Reading $_SERVER['HTTP_CF_CONNECTING_IP'] directly in PHP accepts forged values unless you check that the connection really comes from Cloudflare, so we recommend fixing this in the server configuration instead.
Step 4: Decide whether to cache HTML
With the default behavior of caching only static files, there is no risk of the admin screens or a logged-in view being served to other people. If you want Cloudflare to cache HTML too, such as the front page, use one of the following.
Option A: Automatic Platform Optimization (APO)
APO is a WordPress feature that serves HTML from Cloudflare's edge. To use it, you must install and activate Cloudflare's official WordPress plugin (the plugin named "Cloudflare"). Check Cloudflare's pricing page for the price and which plans include it.
Option B: Make HTML eligible for cache with Cache Rules
Create a Cache Rule (up to 10 on the Free plan) that makes HTML eligible for cache (Eligible for cache). Exclude the admin area, the login page, and visitors who are logged in, have posted a comment, or are viewing a password-protected page. Build the expression in the Expression Builder, or write it like this.
(http.host eq "example.com"
and not starts_with(http.request.uri.path, "/wp-admin")
and not starts_with(http.request.uri.path, "/wp-login.php")
and not starts_with(http.request.uri.path, "/wp-json")
and not http.cookie contains "wordpress_logged_in_"
and not http.cookie contains "comment_author_"
and not http.cookie contains "wp-postpass_")
- Cache eligibility: Eligible for cache
- Edge TTL: if you choose "Ignore cache-control header and use this TTL", keep it short to match how often you update (for example, a few hours) and purge on updates as described in the next step
The origin already sends no-cache-style headers for everything under WordPress's wp-admin, but excluding it explicitly as in the expression above makes configuration mistakes easier to spot. On sites with carts or member pages, such as WooCommerce, also exclude those paths and the cookies the plugin uses (for WooCommerce, woocommerce_items_in_cart and others).
After setting it up, open pages while logged out and while logged in, and check the cf-cache-status response header.
curl -sI https://example.com/ | grep -i cf-cache-status
If it shows HIT from the second request on, the page is cached. DYNAMIC means the response is not eligible for cache, and BYPASS means the cache was skipped because of an exclusion condition or headers.
Step 5: Purge the cache when a post is updated
Once HTML is cached, an updated post keeps showing the old page until the Edge TTL expires. With APO, the Cloudflare plugin does the purging. With Cache Rules, either purge specific URLs from "Purge Cache" in the dashboard, or purge through the API on update as shown below.
First, under "API Tokens" in Cloudflare, create a token that has only the Cache Purge permission for the zone. Do not use the Global API Key. The zone ID is shown on the domain's overview page.
Add this to wp-config.php (keep this file outside the public directory or give it strict permissions).
<?php
define( 'CF_ZONE_ID', 'your-zone-id' );
define( 'CF_API_TOKEN', 'api-token-with-cache-purge-permission-only' );
Add this to your theme's functions.php or to a custom plugin.
<?php
/**
* When a published post is saved, purge the Cloudflare cache for that post and the front page.
*/
add_action( 'save_post', 'mysite_cf_purge_post', 10, 2 );
function mysite_cf_purge_post( int $post_id, WP_Post $post ): void {
if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
return;
}
if ( 'publish' !== $post->post_status ) {
return;
}
if ( ! defined( 'CF_ZONE_ID' ) || ! defined( 'CF_API_TOKEN' ) ) {
return;
}
$urls = array_values( array_unique( array_filter( array(
get_permalink( $post_id ),
home_url( '/' ),
) ) ) );
$response = wp_remote_post(
'https://api.cloudflare.com/client/v4/zones/' . rawurlencode( CF_ZONE_ID ) . '/purge_cache',
array(
'headers' => array(
'Authorization' => 'Bearer ' . CF_API_TOKEN,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array( 'files' => $urls ) ),
'timeout' => 10,
)
);
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
error_log( 'Cloudflare purge failed for post ' . $post_id );
}
}
Purging by URL is available on the Free plan too, and there is a limit on how many URLs one request can contain (100 on the Free plan as of November 2026). If you also cache category or tag archive pages, add those URLs to $urls.
Step 6: Other settings
| Setting | What it does and what to watch for |
|---|---|
| Rocket Loader | Defers JavaScript loading. Because it changes execution order, plugins or the theme may behave differently. If you enable it, check every page |
| Bot Fight Mode | Counters automated traffic. It can block legitimate access from external services (monitoring, integrations, etc.), so check after enabling it |
| HTTP/3 (with QUIC) | Uses HTTP/3 with browsers that support it |
| Polish | Automatic image optimization. Available on Pro and above, not on the Free plan |
| Compression | Compression to visitors uses each plan's default (as of November 2026, Zstandard on Free and Brotli on Pro and Business) and can be changed with Compression Rules |
Auto Minify (minification of HTML, CSS and JS), which used to exist, has been deprecated. The setting described in older tutorials is no longer in the dashboard. If you need minification, do it on the WordPress side, for example with a caching plugin.
Troubleshooting
| Symptom | Main cause and what to check |
|---|---|
ERR_TOO_MANY_REDIRECTS |
The SSL mode is Flexible and the origin redirects to HTTPS. Switch to Full (strict) |
| The admin area or a logged-in view is visible to others | The HTML caching rule is missing exclusion conditions. Disable the rule, clear everything with Purge Everything, and review the conditions |
| Updated posts still show the old version | HTML is being cached. Purge the URL or shorten the Edge TTL |
| Every IP in comments or logs is the same | The real IP restoration in Step 3 is not configured |
| Some scripts don't work | Disable Rocket Loader and check again |
Summary
- Once you switch nameservers, even the Free plan gives you static file caching and HTTPS. HTML is not cached by default.
- For SSL/TLS, install a certificate on the origin and use Full (strict) (or Automatic SSL/TLS). Flexible causes redirect loops.
- Restore visitor IPs on the web server, trusting only Cloudflare's IP ranges.
- If you cache HTML, use APO or Cache Rules, exclude the admin area and logged-in users, and purge the cache on updates.
For Cloudflare's security settings, see also Cloudflare WAF settings.
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)