A client once messaged me in a panic with a screenshot from Munich: an anonymous visitor loaded their homepage and got the full WordPress admin bar—complete with an editor’s username and draft notifications. They had just flipped on full-page HTML edge caching on their CDN to survive a traffic spike, completely forgetting about session checks.
When you cache raw HTML at the edge, every visitor gets whatever payload sits at the nearest POP. If an authenticated user’s response gets cached, your private data goes public. On the flip side, if logged-in users get served an anonymous cache hit, their admin bar vanishes, WooCommerce carts desync, and form nonces break. Here is how I fix this using cookie-based edge bypass rules backed by origin server validation.

The Anatomy of WordPress Session Cookies
To tell an edge server when to bail out of the cache, you need to know how WordPress tracks sessions. It doesn’t use a single generic session token across the board—it drops distinct cookies depending on what the user is doing.
According to the official WordPress Cookie documentation, core uses three main cookies for auth:
wordpress_logged_in_[hash]: Drops right after login. It identifies who you are and verifies your session on the frontend.wordpress_sec_[hash]: Used over HTTPS to secure dashboard access inside/wp-admin/.wp-postpass_[hash]: Set when someone enters a password for a protected post or page.
If you run WooCommerce, it adds its own layer to keep cart states from breaking:
woocommerce_items_in_cart: Dropped the second a customer puts an item in their cart.woocommerce_cart_hash: Tracks cart changes to trigger fragment refreshes.wp_woocommerce_session_[hash]: Stores the unique customer session ID for the cart.
Any CDN sitting in front of WordPress has to inspect the incoming Cookie header for these patterns. If any of these strings appear in the request, the CDN needs to drop the cache and pass the traffic straight back to the origin server.
Step 1: Configure Your Origin Server Cache-Control Headers
Before touching the CDN, your origin (Nginx or Apache) must send the right Cache-Control headers. If PHP outputs Cache-Control: public, max-age=3600 on an authenticated request, an aggressive edge cache will store it anyway, ignoring your request-level rules.
By default, when WordPress detects an authenticated user, it calls nocache_headers():
Cache-Control: no-cache, must-revalidate, max-age=0, no-store, private
Pragma: no-cache
Expires: Wed, 11 Jan 1984 05:00:00 GMTThe problem is that aggressive microcaching plugins or custom FastCGI configurations often overwrite these headers without you realizing it. Check your Nginx FastCGI configuration on the origin.
Open your virtual host file (usually under /etc/nginx/sites-available/your-site.conf):
sudo nano /etc/nginx/sites-available/your-site.confMake sure your Nginx config sets a skip-cache flag based on WordPress cookies before processing the request:
# Default: cache HTML
set $skip_cache 0;
# POST requests and URLs with query strings should skip cache
if ($request_method = POST) { set $skip_cache 1;
}
if ($query_string != "") { set $skip_cache 1;
}
# Skip cache for WordPress auth and checkout cookies
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|woocommerce_items_in_cart") { set $skip_cache 1;
}
# Skip cache for administrative URLs
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") { set $skip_cache 1;
}Inside your location ~ .php$ block, feed that $skip_cache variable directly into your FastCGI directives:
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
add_header X-FastCGI-Cache $upstream_cache_status;If you are also tuning headers for assets, check our guide on Nginx cache-control immutable settings for static assets to keep static files aggressively cached while dynamic HTML bypasses.
Step 2: Configure CDN Edge Cache Bypass Rules
Once your origin sends clean headers, configure your CDN edge nodes to inspect incoming cookies. Whether you use StackPath, Fastly, or Cloudflare, the core logic is identical.
You need an edge rule that scans the Cookie header for WordPress session strings. Edge rules run top to bottom on most CDNs, so put your bypass rule right at the top.
Set up the rule with these parameters:
- Rule Name: Bypass Cache – WordPress Authenticated
- Match Conditions (ANY / OR):
Header.Cookiematches regexwordpress_logged_in_.*Header.Cookiematches regexwp-postpass_.*Header.Cookiematches regexcomment_author_.*Header.Cookiematches regexwoocommerce_items_in_cartHeader.Cookiematches regexwoocommerce_cart_hash
- Execution Action: Bypass Cache (or set Edge Cache TTL to
0sand pass directly to origin)
Keep in mind that background requests need special care too. Take a look at how to bypass CDN edge cache for admin-ajax.php in WordPress so your asynchronous calls don’t return stale payloads.
Varnish / Edge VCL Syntax
If your CDN uses Varnish or Fastly edge nodes, write this directly into your vcl_recv routine:
sub vcl_recv { # Never cache HTTP authorization headers if (req.http.Authorization) { return (pass); } # Bypass cache if WordPress or WooCommerce session cookies are found if (req.http.Cookie ~ "wordpress_logged_in_" || req.http.Cookie ~ "wp-postpass_" || req.http.Cookie ~ "woocommerce_items_in_cart" || req.http.Cookie ~ "woocommerce_cart_hash") { return (pass); } # If no auth cookies exist, strip client cookies to maximize cache hit ratio if (req.http.Cookie) { # Remove non-essential analytics cookies before caching set req.http.Cookie = regsuball(req.http.Cookie, "(^|;s*)(__[a-z]+|has_js)=[^;]*", ""); set req.http.Cookie = regsub(req.http.Cookie, "^;s*", ""); if (req.http.Cookie == "") { unset req.http.Cookie; } }
}If any of those cookies are present, this forces a direct pass to your origin, completely skipping edge storage.
What I Ran: Validating Edge Bypass with cURL
Never rely on your browser’s DevTools to test this. Local browser caches and open sessions produce endless false positives.
I test directly from the terminal using curl to hit the edge POP with and without authentication cookies. Here is the exact check I ran to confirm our fix worked.
First, test an anonymous request to confirm the edge is caching public traffic:
curl -I -s https://example.com/The response headers show a clean edge hit:
HTTP/2 200
date: Tue, 18 Mar 2025 14:22:10 GMT
content-type: text/html; charset=UTF-8
cache-control: max-age=3600, public
age: 412
x-cache: HIT
x-cache-hits: 14Next, send a request with a mock wordpress_logged_in_ cookie attached:
curl -I -s -b "wordpress_logged_in_a1b2c3d4e5f6=testuser" https://example.com/Check the headers returned by the POP:
HTTP/2 200
date: Tue, 18 Mar 2025 14:22:15 GMT
content-type: text/html; charset=UTF-8
cache-control: no-cache, must-revalidate, max-age=0, no-store, private
x-cache: BYPASS
x-fastcgi-cache: BYPASSNotice the critical differences:
x-cache: BYPASS(orMISS/EXPIREDdepending on the provider) confirms the edge skipped cached HTML entirely.cache-controlshowsno-store, private, coming straight from WordPress core.- TTFB for the anonymous request was 22ms at the edge; the authenticated request took 487ms while the origin processed PHP.
The Gotcha: How “Vary: Cookie” Can Destroy Hit Rates
I ran into a nasty surprise on a client’s Nginx server where someone had dropped add_header Vary "Accept-Encoding, Cookie"; into the config, assuming it was a safe fallback.
As explained in the MDN documentation on HTTP caching headers, the Vary header tells caches to store a unique copy of the page for every variation of that header sent by clients.
When you set Vary: Cookie on public HTML, nearly every visitor gets a distinct cache bucket because of third-party cookies like _ga or _fbp. Your edge cache hit ratio drops from 95% down to under 5%, and your origin server CPU spikes to 100% almost immediately.
Here is how to resolve it:
- Strip
Cookiefrom the origin’sVaryresponse header on public pages. Stick toVary: Accept-Encoding. - Handle cookie checks through Edge Rules instead of relying on HTTP
Vary. - Configure your CDN to strip analytics and marketing cookies from the edge cache key. See our walkthrough on how to configure CDN edge cache rules to ignore tracking query strings for related cache key hygiene.
If you run an object cache alongside your edge setup, you can also enable Redis object cache for WordPress to keep origin queries fast when logged-in users bypass the edge.
Handling the WP REST API and Nonce Invalidation
Even after your cookie bypass rules are active, you might notice broken forms or Gutenberg blocks failing to save. This is almost always due to stale REST API nonces.
WordPress inlines a nonce (wpApiSettings.nonce) directly into page markup. If an authenticated user accidentally receives cached HTML meant for an anonymous visitor, that nonce will be invalid. Any POST request sending X-WP-Nonce fails with a 403 Forbidden:
{ "code": "rest_cookie_invalid_nonce", "message": "Cookie check failed", "data": { "status": 403 }
}To avoid this, make sure your bypass rules cover the /wp-json/ endpoint for write requests, or refresh your nonces dynamically via an un-cached script.
Add a dedicated rule at the edge for your REST endpoints:
- Path Condition: Request URI starts with
/wp-json/ - Method Condition: Method does NOT equal
GET - Action: Bypass Edge Cache
Troubleshooting Checklist
If you applied these rules and logged-in users are still seeing cached pages, check these four things:
- Check for Cookie Stripping: Make sure your CDN or a proxy layer isn’t stripping incoming
Cookieheaders before evaluating rules. Some optimization plugins strip them blindly. - Subdomain Cookie Scope: If your blog lives on
blog.example.combut authentication happens atexample.com, checkCOOKIE_DOMAINinwp-config.php. If it isn’t set to.example.com, the edge node serving the subdomain won’t see the auth cookie. - Inspect Cache Plugins: If you use plugins like WP Rocket, W3 Total Cache, or LiteSpeed Cache, verify their internal “Do not cache pages for logged-in users” options are turned on.
- Custom Login Paths: If you use a plugin that moves
wp-login.phpto a custom URI like/my-custom-login/, add that custom path to your edge bypass rules.
Frequently Asked Questions
Why does my CDN still serve cached pages after I log in?
Your edge rule is probably using an exact string match instead of a regex prefix. WordPress appends a dynamic hash to the cookie name (like wordpress_logged_in_49d7f...). Your CDN rule must target the prefix wordpress_logged_in_.*, not the literal string wordpress_logged_in.
Does bypassing the edge cache for logged-in users hurt performance?
Not for normal setups. 95% or more of your traffic will be anonymous visitors hitting the edge cache. Bypassing cache for the tiny slice of editors and active shoppers leaves origin server load minimal while keeping the site functional for your team.
Can I cache the admin dashboard if I am the only editor?
No. Never cache anything inside /wp-admin/ or any request sending auth cookies. WordPress relies on dynamic nonces, session checks, and state queries that break completely when served from cache.
What happens if a user logs out?
When a user logs out, WordPress clears the auth cookies by setting their expiration date in the past. On the next request, the browser sends no matching cookies, and the CDN goes back to serving cached public HTML.
Next Steps
Once you deploy these rules, keep an eye on your CDN analytics for 24 hours. You want to see edge cache hit ratios holding above 90% with zero session bleed issues. If your origin struggles under bypassed requests, check out our guide on configuring Redis object caching to cut down origin database latency for logged-in users.

