Getting the most out of Cloudflare’s cache means sending the right Cache-Control headers from your origin. WordPress doesn’t do this by default — it either sends no-cache or nothing at all. Drop this into your theme’s functions.php or a must-use plugin.
<?php
/**
* Set edge-friendly Cache-Control headers.
* - Static logged-out pages: cache at edge for 1 hour, stale-while-revalidate 5 min
* - Logged-in users: bypass cache entirely
* - WooCommerce cart/checkout: always private
*/
add_action( 'send_headers', function () {
// Never cache for logged-in users
if ( is_user_logged_in() ) {
header( 'Cache-Control: no-store, private' );
return;
}
// Never cache WooCommerce dynamic pages
if ( function_exists( 'is_woocommerce' ) ) {
if ( is_cart() || is_checkout() || is_account_page() ) {
header( 'Cache-Control: no-store, private' );
return;
}
}
// Cache everything else at the edge
header( 'Cache-Control: public, max-age=3600, stale-while-revalidate=300, stale-if-error=86400' );
} );
Paired Cloudflare Cache Rule
In your Cloudflare dashboard, create a Cache Rule that matches your hostname and sets Cache Status to Cache Everything. The Cache-Control header above will govern TTLs — Cloudflare respects max-age when this rule is active.
Purging on publish
Pair this with a publish hook to purge the Cloudflare cache when a post is updated:
add_action( 'save_post', function ( $post_id ) {
if ( wp_is_post_revision( $post_id ) ) return;
$zone_id = defined( 'CF_ZONE_ID' ) ? CF_ZONE_ID : '';
$token = defined( 'CF_API_TOKEN' ) ? CF_API_TOKEN : '';
if ( ! $zone_id || ! $token ) return;
wp_remote_post(
"https://api.cloudflare.com/client/v4/zones/{$zone_id}/purge_cache",
[
'headers' => [
'Authorization' => "Bearer {$token}",
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( [ 'purge_everything' => true ] ),
]
);
} );
Define CF_ZONE_ID and CF_API_TOKEN as constants in wp-config.php, never hardcoded.