Cloudflare and WordPress
Putting a proxy in front of WordPress solves real problems and introduces a specific set of new ones — redirect loops, mixed content, cached logged-in pages and a visitor IP that is no longer the visitor.
Cloudflare sits between visitors and your server. DNS points at them, they answer the request, and they decide whether to serve something cached, apply a rule, or fetch from your origin. That single change gives you a CDN, a firewall and DDoS protection — and it introduces a set of problems that all have the same root cause: WordPress is no longer talking to the visitor directly.
What it genuinely gives you
- Static assets served from an edge near the visitor, which is most of the page weight on a typical site.
- A firewall in front of PHP, so blocked traffic never costs you a process.
- DDoS absorption at a scale no single host provides.
- Free TLS, including for sites whose host makes certificates awkward.
- DNS that is fast and easy to change, which matters during a migration.
None of that replaces server-side caching. A CDN accelerates delivery; it does not make WordPress assemble pages faster. Keep your page cache and treat the CDN as the layer in front.
Get the SSL mode right first
This is the single most common Cloudflare-on-WordPress failure, and it presents as an infinite redirect.
| Mode | What it does | Verdict |
|---|---|---|
| Off | No encryption | Never |
| Flexible | HTTPS to visitor, plain HTTP to your server | The redirect-loop generator |
| Full | HTTPS to origin, certificate not validated | Acceptable stopgap |
| Full (strict) | HTTPS to origin with a valid certificate | Correct |
Flexible breaks WordPress reliably: the proxy sends HTTP to your origin, your origin redirects HTTP to HTTPS, the redirect goes back through the proxy, and around it goes. Install a valid certificate on the origin — Cloudflare will issue an origin certificate free — and use Full (strict).
If the loop persists after switching, check for a second redirect source: a "force HTTPS" setting in a plugin, an .htaccess rule, or WP_HOME and WP_SITEURL in wp-config.php still on http://.
Tell WordPress the request was secure
Because TLS terminates at the proxy, PHP sees a plain HTTP request. is_ssl() returns false, WordPress generates http:// URLs, and you get mixed content and redirect oddities. Fix it near the top of wp-config.php, before wp-settings.php is required:
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$_SERVER['HTTPS'] = 'on';
}
Only do this behind a proxy you actually control — trusting a client-supplied header on a directly exposed server is a security hole. The rest of the clean-up is in how to fix mixed content warnings.
Restore the real visitor IP
Every request now arrives from a Cloudflare address. Left alone, this quietly breaks a lot:
- security plugins see one visitor and cannot rate-limit or ban anyone;
- comment spam filtering loses its main signal;
- analytics and logs are useless;
- a login lockout locks out everybody at once.
Use the host's Cloudflare integration if it has one, or a module that reads CF-Connecting-IP. Verify it rather than assume it: check that a security plugin's log shows varied addresses. Until it does, security tooling is operating blind.
Caching HTML, carefully
By default HTML passes through and only static assets are cached. Caching HTML at the edge is a large speed win for anonymous visitors and a serious hazard for anything personalised.
Before enabling it, guarantee that these are never served from cache:
/wp-admin/andwp-login.php;- cart, checkout, my-account and order-received;
- any page that differs per logged-in user;
- REST and AJAX endpoints used by forms and search;
- anything behind a membership.
And confirm the cache bypasses on the logged-in cookie. A cached account page shown to the wrong person is the worst outcome available here, and it comes from a missing rule rather than from a bug.
Test it the direct way: put an item in the cart, open the site in a private window, and confirm the cart is empty there. Related endpoint symptoms are covered in WooCommerce endpoints not working.
Rules worth setting
- Bypass cache for
/wp-admin/*and the store endpoints. - A rate limit or challenge on
wp-login.php, which absorbs most brute-force traffic before it reaches PHP. Combine it with two-factor authentication. - Leave
/wp-json/reachable unless you are certain nothing depends on it — the block editor does. - Do not block
wp-cron.phpunless you have replaced WP-Cron with a system cron. - Be conservative with minification and JavaScript deferral at the edge. They break the same things they break in a plugin: menus, forms and sliders.
Purging
You now have at least two caches, sometimes three. When a change does not appear, purge in order: the WordPress page cache, then Cloudflare, then your browser — how to clear the WordPress cache covers the first, and WordPress changes not showing covers the diagnosis when it still does not.
Cloudflare's official WordPress plugin exists mainly to purge the edge automatically when content changes. If you cache HTML, you want that integration rather than manual purges.
Common mistakes
- Flexible SSL, producing the redirect loop that starts most of these investigations.
- Enabling HTML caching without exclusions, then serving a logged-in page to a stranger.
- Not restoring visitor IPs, then wondering why the firewall never blocks anything.
- Development mode left on, so nothing is cached and the site is slower than before.
- Assuming the CDN replaces a page cache. It does not.
- Proxying a record that must resolve to the real server — mail, or an API endpoint a payment provider calls back.
Verify
Logged out and in a private window: the padlock is present with no console warnings, http:// redirects once to https:// with no chain, a post loads and shows a cache hit on the second request, and /wp-admin/ shows a bypass. Logged in: the admin works, changes appear after a purge, and the cart is yours alone. Then check that your security plugin's log shows real, varied visitor addresses — if it does not, nothing else on this list is being enforced properly.
Frequently asked
- Almost always the SSL mode. Flexible sends plain HTTP to an origin that redirects to HTTPS, which comes back through the proxy forever. Use Full (strict) with a valid certificate on the origin.
- No. By default it caches static assets and passes HTML through. Caching HTML is opt-in, and doing it without excluding logged-in users, the cart and the checkout is how one visitor sees another's page.
- Requests reach your server from Cloudflare, so the connecting IP is theirs. Restore the real one with the host's Cloudflare module or by trusting the CF-Connecting-IP header, or every security plugin will see one visitor.