uiring zero changes to the WordPress codebase.
Step 1: Construct the Cache Rule
WordPress requires selective caching. Public HTML should be cached aggressively, while administrative interfaces, authentication endpoints, REST API calls, and authenticated sessions must bypass the cache entirely.
Architecture Decision:
- Exclusion Logic: Group path exclusions using a logical
OR inside a NOT clause. This pattern is more maintainable than chaining multiple AND NOT conditions and reduces the risk of accidentally caching sensitive endpoints.
- Session Awareness: The
wordpress_logged_in cookie indicates an active authenticated session. Caching content for logged-in users risks serving personalized data to unauthorized visitors. The rule explicitly checks for this cookie to bypass caching.
- API Isolation: The
/wp-json path handles dynamic REST API requests, including form submissions, cart updates, and plugin AJAX handlers. Caching these responses breaks stateful operations and nonce validation.
Implementation:
Navigate to Rules → Overview → Cache Rules → Create rule.
- Rule Name:
WordPress HTML Cache
- When incoming requests match: Select Edit expression and input the following logic:
(
http.host eq "yourdomain.com"
and not (
starts_with(http.request.uri.path, "/wp-admin")
or starts_with(http.request.uri.path, "/wp-login.php")
or starts_with(http.request.uri.path, "/wp-json")
)
and not http.cookie contains "wordpress_logged_in"
)
Replace yourdomain.com with your actual production domain.
-
Then configure the following:
- Cache eligibility: Set to
Eligible for cache.
- Edge TTL: Select
Ignore cache-control header and set duration to 2 hours.
- Rationale: WordPress core and many themes emit
Cache-Control: no-cache or private headers by default. If Cloudflare respects these directives, HTML will never be cached regardless of your TTL configuration. Ignoring origin cache-control forces the edge to cache based on your explicit TTL.
- Browser TTL: Select
Override origin and set duration to 1 hour.
- Rationale: The browser TTL must remain lower than the Edge TTL. This ensures clients revalidate with the edge after one hour, allowing the edge to serve fresh content if the origin has updated, while the edge retains the object for two hours to maximize hit rates and reduce origin fetches.
-
Deploy the rule.
Security headers mitigate common vulnerabilities including MIME-type sniffing, clickjacking, protocol downgrade attacks, and referrer leakage. Implementing these via Transform Rules ensures they are applied consistently to all responses, including cached HTML, without adding PHP execution time.
Implementation:
Navigate to Rules → Overview → Create rule → Response Header Transform Rule.
-
Rule Name: WordPress Security Headers
-
When incoming requests match: Select All incoming requests.
-
Then configure the following headers:
- Header 1:
- Header Name:
Strict-Transport-Security
- Value:
max-age=31536000; includeSubDomains
- Action:
Set static
- Header 2:
- Header Name:
X-Content-Type-Options
- Value:
nosniff
- Action:
Set static
- Header 3:
- Header Name:
X-Frame-Options
- Value:
SAMEORIGIN
- Action:
Set static
- Header 4:
- Header Name:
Referrer-Policy
- Value:
strict-origin-when-cross-origin
- Action:
Set static
-
Deploy the rule.
Step 3: Verification Protocol
Validation requires checking both the cache status and the presence of security headers. The following Python utility uses a class-based structure to validate the configuration without external dependencies.
import urllib.request
import sys
import logging
class EdgeConfigValidator:
def __init__(self, target_url: str):
self.target_url = target_url
self.logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
def _fetch_headers(self) -> dict:
req = urllib.request.Request(self.target_url, method='GET')
req.add_header('User-Agent', 'EdgeValidator/1.0')
with urllib.request.urlopen(req) as response:
return dict(response.headers)
def validate_cache_status(self, headers: dict) -> bool:
cache_status = headers.get('CF-Cache-Status')
valid_statuses = {'HIT', 'EXPIRED', 'REVALIDATED', 'DYNAMIC'}
if cache_status not in valid_statuses:
self.logger.error(f"Unexpected cache status: {cache_status}")
return False
if cache_status == 'DYNAMIC':
self.logger.warning("Cache status is DYNAMIC. Verify rule deployment and exclusions.")
return False
self.logger.info(f"Cache status validated: {cache_status}")
return True
def validate_security_headers(self, headers: dict) -> bool:
required = {
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'SAMEORIGIN',
'Referrer-Policy': 'strict-origin-when-cross-origin'
}
all_valid = True
for header, expected in required.items():
actual = headers.get(header)
if actual != expected:
self.logger.error(f"Header mismatch: {header} | Expected: {expected} | Got: {actual}")
all_valid = False
else:
self.logger.info(f"Header validated: {header}")
return all_valid
def run(self) -> int:
try:
headers = self._fetch_headers()
cache_ok = self.validate_cache_status(headers)
security_ok = self.validate_security_headers(headers)
return 0 if (cache_ok and security_ok) else 1
except Exception as exc:
self.logger.error(f"Validation failed: {exc}")
return 1
if __name__ == "__main__":
target = sys.argv[1] if len(sys.argv) > 1 else "https://yourdomain.com"
sys.exit(EdgeConfigValidator(target).run())
Execute with: python edge_validator.py https://yourdomain.com
For rapid terminal checks, use:
curl -I https://yourdomain.com | grep -E "CF-Cache-Status|Strict-Transport|X-Content-Type|X-Frame|Referrer-Policy"
Pitfall Guide
-
Admin Path Leakage
Explanation: Omitting /wp-admin or /wp-login.php from exclusions causes the edge to cache login forms and dashboard interfaces. This can lock administrators out of the CMS or serve stale admin panels to unauthorized visitors.
Fix: Always include explicit path exclusions for all administrative, authentication, and setup endpoints in the cache rule expression.
-
Cache-Control Override Failure
Explanation: WordPress core emits Cache-Control: no-cache by default. If Cloudflare respects these headers, HTML will never be cached, regardless of your Edge TTL configuration.
Fix: Set Edge TTL to Ignore cache-control header. This forces the edge to cache based on your explicit TTL, overriding origin directives that would otherwise disable caching.
-
Authenticated Session Contamination
Explanation: Without a cookie check, the CDN may cache a personalized page (e.g., a user profile or dashboard preview) and serve it to other visitors, causing data leakage.
Fix: Include not http.cookie contains "wordpress_logged_in" in the cache rule expression to bypass caching for authenticated sessions.
-
REST API & AJAX Breakage
Explanation: Caching /wp-json or POST requests breaks dynamic functionality, form submissions, and plugin AJAX handlers. Nonce validation fails when cached responses are replayed.
Fix: Exclude /wp-json paths. If your site relies heavily on POST-based AJAX, restrict the cache rule to GET requests only using http.request.method eq "GET".
-
HSTS Rollout Missteps
Explanation: Setting a long max-age for Strict-Transport-Security without testing can make the site inaccessible if SSL is misconfigured, expired, or temporarily removed. The preload directive permanently locks browsers into HTTPS.
Fix: Start with a shorter max-age (e.g., 300 seconds) during testing. Increase to 31536000 only after verifying SSL stability. Avoid the preload flag unless you have a long-term HTTPS commitment and redundant certificate management.
-
TTL Hierarchy Inversion
Explanation: If Browser TTL exceeds Edge TTL, the client may serve stale content while the edge has already expired the object. This creates inconsistent behavior and forces unnecessary origin fetches.
Fix: Ensure Edge TTL is greater than or equal to Browser TTL. The recommended configuration is Edge TTL > Browser TTL to allow edge revalidation without client-side staleness.
-
Stale Content Persistence
Explanation: After publishing or updating content, cached pages may serve outdated versions until the TTL expires. Editors and SEO teams often report "missing updates" as a result.
Fix: Implement an automated purge workflow. Use the Cloudflare API or dashboard to purge specific URLs or tags immediately after content updates. Integrate a webhook with WordPress's save_post action to trigger edge purges programmatically.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| High-Traffic Blog / Media Site | Edge-Optimized Cache + Full Security Headers | Maximizes cache hit ratio, absorbs traffic spikes, reduces origin scaling costs | Lowers compute costs by 60-80% |
| WooCommerce / E-commerce | Edge Cache for static pages + Bypass for /cart, /checkout, /my-account | Preserves dynamic cart state while caching product/category pages | Moderate origin load; requires careful path exclusion |
| Development / Staging | Disable Edge Caching + Security Headers Only | Prevents stale content during active development while maintaining security posture | Zero performance gain; ensures accurate testing |
| Multi-Site Network | Single Cache Rule with wildcard host matching + path exclusions | Centralizes policy management across multiple domains/subdomains | Reduces administrative overhead; scales linearly |
Configuration Template
Copy the following expressions directly into Cloudflare's rule builder. Replace yourdomain.com with your production domain.
Cache Rule Expression:
(
http.host eq "yourdomain.com"
and not (
starts_with(http.request.uri.path, "/wp-admin")
or starts_with(http.request.uri.path, "/wp-login.php")
or starts_with(http.request.uri.path, "/wp-json")
)
and not http.cookie contains "wordpress_logged_in"
and http.request.method eq "GET"
)
Cache Rule Actions:
- Cache eligibility:
Eligible for cache
- Edge TTL:
Ignore cache-control header → 2 hours
- Browser TTL:
Override origin → 1 hour
Security Headers (Response Header Transform Rule):
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Quick Start Guide
- Navigate to Cloudflare Dashboard: Open your domain, go to Rules → Overview → Cache Rules, and click Create rule.
- Paste the Expression: Input the cache rule expression from the Configuration Template. Set Edge TTL to
Ignore cache-control header (2 hours) and Browser TTL to Override origin (1 hour). Deploy.
- Add Security Headers: Go to Rules → Overview → Create rule → Response Header Transform Rule. Set match condition to
All incoming requests. Add the four security headers with static values. Deploy.
- Verify & Purge: Run the verification script or use
curl -I. Purge all cache from the Cloudflare dashboard to force a clean state. Monitor CF-Cache-Status in subsequent requests to confirm HIT behavior.