Cloudflare for WordPress: full performance and security optimization
How to configure Cloudflare (Free/Pro) for WordPress step by step: cache, APO, WAF, Bot Fight Mode, page rules, CDN. Real measurement results and configuration for Poland.
Cloudflare for WordPress: full optimization
Cloudflare is the best free thing you can do for your WordPress. For 90% of sites the Free plan is enough β it gives CDN, cache, SSL, basic WAF. This post shows the full configuration I use at every client.
What Cloudflare gives you for free
| Feature | Free plan | Pro plan ($20/m) | |---------|-----------|------------------| | CDN (static asset cache) | β | β | | Universal SSL | β | β | | Basic WAF (3 rules) | β | β | | Rate limiting (1 rule) | β | 5 rules | | DDoS protection (L3/L4) | β | β | | Bot Fight Mode | β | β advanced | | Page Rules | 3 | 20 | | Workers (serverless) | 100k/day | 10M/day | | APO (HTML cache) | β ($5/m) | β in Pro price | | Image Resizing | β | β | | Web Analytics | β | β |
My recommendation: for blogs and small sites β Free + APO ($5/m) for $5/month total. For e-commerce and SaaS β Pro ($20/m).
Step 1: Add the site to Cloudflare
# 1. Create an account on cloudflare.com
# 2. Add the domain β Cloudflare scans DNS records
# 3. Change nameservers at the registrar (OVH, namecheap) to:
# ada.ns.cloudflare.com
# bob.ns.cloudflare.com
# (Cloudflare gives you the right ones)
# 4. Wait 24-48h for DNS propagation
After propagation all traffic goes through Cloudflare. Your server (origin) is no longer directly visible from the internet β attackers see only Cloudflare IPs. That alone is basic DDoS protection.
Step 2: SSL/TLS
Crypto β SSL/TLS set to Full (strict). This enforces end-to-end HTTPS (Cloudflare β origin) with certificate validation.
SSL/TLS β Overview β Full (strict)
Why not "Flexible"? Flexible encrypts only Cloudflare β user, but Cloudflare β origin goes over HTTP. Someone on the origin provider network could sniff it.
On the origin server you need a valid SSL certificate. The easiest: Let's Encrypt via Certbot:
# On the origin server
sudo apt install certbot python3-certbot-apache # for Apache
# or
sudo apt install certbot python3-certbot-nginx # for Nginx
sudo certbot --nginx -d example.com -d www.example.com
# or
sudo certbot --apache -d example.com -d www.example.com
Certbot auto-renews the certificate every 60 days.
Step 3: Cache configuration
Caching β Configuration:
Caching Level: Standard
Browser Cache TTL: 4 hours
Crawler Hints: ON
Always Online: ON
Standard cache level caches everything possible except dynamic endpoints. For WP without APO this caches CSS, JS, images, fonts β i.e. 80% of requests.
Page Rules (free: 3 rules)
Page Rules are the most powerful feature of the free plan. Three rules that give the most:
# Rule 1: Bypass cache for logged-in users
URL: *example.com/wp-admin*
Cache Level: Bypass
Security Level: High
# Rule 2: Cache HTML for posts (24h)
URL: *example.com/*
Cache Level: Cache Everything
Edge Cache TTL: 1 day
Browser Cache TTL: 4 hours
Cache Everything caches HTML on the Cloudflare edge. Without APO this works only when Cloudflare sees cache-control headers from origin. WordPress does not send them by default β you need the Cloudflare plugin (official) or WP Super Cache.
Cloudflare plugin for WordPress
# Install the official Cloudflare plugin
wp plugin install cloudflare --activate
# Enable automatic cache purge on update
# Set the API token in plugin settings
The plugin also adds auto-purge cache on post publish. Without it you have to click "Purge Cache" manually in Cloudflare after every update.
Step 4: Performance optimizations
Speed β Optimization:
Auto Minify: HTML, CSS, JS (β)
Brotli: ON
Early Hints: ON
Rocket Loader: ON (test, sometimes breaks JS)
Auto Minify compresses CSS/JS/HTML on the fly. For most sites that's a 10-20% size reduction. Exception: sites with inline critical CSS β minify can break ordering.
Brotli is modern compression (better than gzip). Supported by 95% of browsers. Gives 15-20% smaller transfers than gzip.
Rocket Loader loads JS asynchronously. For simple sites it speeds up rendering. For complex applications (WooCommerce with lots of JS) it can cause errors β test.
Step 5: Security (WAF + Bot Fight Mode)
Security β WAF β Managed Rules: enable Cloudflare Managed Ruleset. A free set of rules protecting against known attacks (SQL injection, XSS, PHP exploits). Zero configuration, zero false positives on a typical WP site.
Security β Bots β Bot Fight Mode: enable.
Security β Settings β Challenge Passage: 30 minutes.
Three custom WAF rules I add
# Rule 1: Block XML-RPC (commonly attacked)
Expression: http.request.uri.path contains "/xmlrpc.php"
Action: Block
# Rule 2: Protect wp-login (rate limit)
Expression: http.request.uri.path contains "/wp-login.php"
Rate limit: 5 requests per 10 seconds
Action: Managed Challenge
# Rule 3: Block scanner User-Agents
Expression:
(http.user_agent contains "nikto") or
(http.user_agent contains "sqlmap") or
(http.user_agent contains "masscan")
Action: Block
XML-RPC is an old WP protocol (the equivalent of SOAP), today
practically unnecessary, but bots attack it because /xmlrpc.php
has simple "check if the password is correct" methods. Blocking it
= zero brute-force attempts on that endpoint.
Step 6: APO (HTML cache) - optional $5/m
APO is the most important optimization for WP. It caches HTML on the Cloudflare edge. Without APO HTML is generated by origin (PHP + MySQL) on every request. With APO β HTML flies from the nearest Cloudflare edge node.
Enabling APO:
- Cloudflare Dashboard β Speed β Optimization β APO
- Enable (costs $5/m/site for Free plan, free in Pro)
- Install the Cloudflare plugin in WP, give it the API token
- Enable "Auto-Purge on Update" in the plugin
Real results (from my last 8 projects):
| Metric | Without APO | With APO | |--------|-------------|----------| | TTFB (cache miss) | 600-1200ms | 400-800ms (origin) | | TTFB (cache hit) | 400-600ms | 30-80ms | | LCP (Largest Contentful Paint) | 2.5-4.0s | 1.2-2.0s | | Load time (P75) | 4.5-6.0s | 1.5-2.5s | | Traffic to origin | 100% | 5-15% |
These are numbers I have seen, not marketing promises. For sites with >5k page views/day APO pays back in a week (less traffic to the server = cheaper hosting).
Step 7: Rate Limiting
Security β WAF β Rate Limit Rules (free plan: 1 rule):
URL: *example.com/wp-login.php
Rate: 5 requests per 10 seconds
Action: Block for 600 seconds
This protects against brute-force password attacks. Without it a bot tries 1000 password combinations per second from one IP.
For the Pro plan you have 5 rules. My typical configuration:
/wp-login.phpβ 5 req/10s/wp-admin/admin-ajax.phpβ 20 req/10s (for AJAX)/wp-json/*β 30 req/10s (for REST API)- Global β 200 req/10s per IP (prevention)
Step 8: DNS β do not expose the origin IP
Critical mistake: your origin server has an IP. Someone can scan it directly, bypassing Cloudflare. Solution:
-
Force traffic through Cloudflare: in Cloudflare DNS turn off proxy for A records (orange cloud β grey cloud for the origin). Set origin IP in the server firewall to accept only Cloudflare IPs.
-
Hide origin IP behind Cloudflare Tunnel (free):
# On the origin server sudo cloudflared service install <tunnel-token> -
Do not send origin IP in emails (SMTP header leak). Set in
wp-config.php:// Disable leaky Server header header_remove('X-Powered-By'); header_remove('Server');
Step 9: Monitoring and testing
# Check whether traffic goes through Cloudflare
curl -I https://example.com
# Look for: server: cloudflare
# Check cache status
curl -I https://example.com | grep -i cf-cache-status
# HIT / MISS / DYNAMIC / BYPASS / EXPIRED
# Test from different locations (free)
# https://www.webpagetest.org
# https://tools.pingdom.com
# Cloudflare Web Analytics (free for Pro)
# Dashboard β Analytics β Web Analytics
Diagnosing cache miss
When you see a lot of cf-cache-status: DYNAMIC, check:
- Query string in the URL β Cloudflare by default does not cache
URLs with
?utm_source=.... A Page Rule with "Ignore Query String" fixes it. - WP cookie β when a user is logged in, Cloudflare does not cache. For not logged in: it works.
- Vary header β if the origin sends
Vary: User-Agent, cache splits into N variants. In WP addVary: Accept-Encodinginstead.
Common configuration mistakes
1. Enabling "Cache Everything" without a Page Rule for wp-admin
Effect: logged-in users see a cached version of the page, nobody can
log in. Fix: a Page Rule with "Cache Level: Bypass" for
/wp-admin/* and /wp-login.php.
2. Disabling proxy in DNS
When a user turns off the orange cloud (proxy) on the A record, traffic goes directly to origin. Zero protection, zero cache. Always proxy=on for the main domain and subdomains (www, cdn, etc.).
3. No Cloudflare plugin in WP
Without the plugin cache is not cleared after a post update. The user publishes an article, still sees the old version for 24h. Fix: plugin + auto-purge.
What is next
If you want me to set up Cloudflare for you (with Cloudflare Tunnel to hide origin IP, optimal cache and WAF) β get in touch. 1-2 days of work, starting from 800 PLN.
NajczΔΕciej zadawane pytania
Is Cloudflare Free enough for WordPress?
What is APO (Automatic Platform Optimization) and is it worth it?
How does Cloudflare affect SEO?
Will Bot Fight Mode block Google crawlers?
Related posts
- wordpress
WordPress REST API: practical use cases for custom post types
How to use WordPress REST API with custom post types in production. Concrete examples: headless CMS, mobile app backend, AI integrations. PHP + JS code ready to use.
8 min - devops
Own mail server: Postfix + Dovecot + SPF/DKIM/DMARC on Debian 12
Full tutorial to set up your own mail server on Debian 12 with Postfix, Dovecot, OpenDKIM, SPF and DMARC. Step-by-step configuration, troubleshooting, real pitfalls.
6 min