Aggressive edge caching makes a WordPress site feel blisteringly fast—right up until WooCommerce cart updates freeze, checkout nonces expire, and dynamic forms spit back random -1 or 0 errors. If you’ve ever watched a user click “Add to Cart” three times with zero feedback, you know the sinking feeling of realizing your CDN cached a dynamic response.
I ran straight into this mess on an e-commerce site with heavy frontend AJAX filtering. The TTFB was incredible, but users kept seeing stale cart totals because StackPath was happily caching admin-ajax.php calls. Here’s how to write a targeted EdgeRule to bypass caching on that endpoint completely while keeping full-page caching and static assets humming along untouched.

The admin-ajax.php Caching Trap
WordPress still funnels almost all legacy frontend asynchronous requests through a single file: /wp-admin/admin-ajax.php. Everything from WooCommerce mini-carts and form plugins to live search widgets post data here or poll it for HTML fragments. Because it lives under /wp-admin/, CDNs with poorly scoped wildcard rules or blanket “cache everything” policies will happily grab those responses and serve them to everyone.
When an edge node caches a GET request to admin-ajax.php, every visitor gets the payload meant for the first person who triggered it. Worse, WordPress embeds security nonces in those responses. When that nonce expires on your origin server 12 to 24 hours later, every subsequent request from cached pages fails its CSRF check. Dynamic features just die silently for logged-out visitors.
You definitely need clean WordPress browser cache headers on Nginx, but edge CDNs like StackPath sit in front of Nginx and need explicit rules. If a plugin forgets to send a strict Cache-Control: no-store header alongside a 200 OK, the CDN might just store it anyway unless you tell it not to.
Inspecting admin-ajax.php Responses in DevTools
Before changing anything in StackPath, check what headers your AJAX calls are actually returning right now. Pop open browser DevTools, click the Network tab, filter by Fetch/XHR, and trigger an AJAX action on your site—like tweaking a filter dropdown or adding an item to your cart.
Click on the admin-ajax.php request and check the Response Headers. You’re hunting for cache indicators from both the CDN and your origin.
You can also pull the exact edge response headers directly from your terminal using cURL:
curl -I -X POST "https://example.com/wp-admin/admin-ajax.php?action=my_test_action" -H "Accept: application/json" -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64)"Look at what the edge proxy sends back:
HTTP/2 200
date: Wed, 18 Oct 2023 14:22:10 GMT
content-type: application/json; charset=UTF-8
x-cache: HIT
x-hw: 1697638930.dop014.pa2.t, 1697638930.cds022.pa2.c
cache-control: max-age=3600, publicIf you see x-cache: HIT or a public cache-control max-age header on an endpoint that serves dynamic user state, your CDN is intercepting it. We want to see x-cache: BYPASS or x-cache: MISS on every single request, without exception.
Understanding EdgeRules Evaluation Order in StackPath
StackPath evaluates EdgeRules sequentially, top to bottom. Each rule checks IF conditions, then fires THEN actions.
Execution order is critical here. If you have a blanket catch-all rule sitting higher up that caches *.php or matches wide URL paths, that rule wins. If your bypass rule sits below it, the edge serves the cached object before it ever reads your exception.
Rules that bypass caching or strip sensitive headers belong at the very top of your stack. We want requests hitting admin-ajax.php to trigger an immediate bypass directive, skipping edge storage and any origin shield caches entirely.
Creating the admin-ajax.php Bypass EdgeRule
Log into your StackPath portal, head to CDN > Sites, and pick your domain. In the left sidebar, click EdgeRules and hit Add EdgeRule.
- Rule Name: Give it something obvious, like
Bypass Cache - WP Admin AJAX. - If (Conditions): Set the dropdown to
URL, choosematches, and enter/wp-admin/admin-ajax.php*. - Add Condition (Optional): If your theme or plugins hit the REST API for live frontend state, add an
ORcondition forURL matches /wp-json/*. - Then (Actions): Choose Edge Cache Policy and set it to Bypass (or Don’t Cache).
- Add Action: Choose Browser Cache Policy and set it to Follow Origin or force No Cache.
Here’s what that logic looks like in pseudo-config:
IF Request.URL matches "*/wp-admin/admin-ajax.php*"
THEN Set Edge Caching Policy = Bypass Cache Set Origin Shield Policy = Bypass Set Browser Cache Policy = No Cache (max-age=0, must-revalidate)Save it, then drag the rule straight to the top of your EdgeRules list. StackPath usually pushes rule updates out to edge nodes in 30 to 60 seconds.
If you’re using origin shields on high-traffic sites, it’s also worth checking how your setup handles CDN origin shielding to prevent cache stampedes once requests start hitting your origin directly.
Configuring Origin Fallbacks in Nginx for admin-ajax
The CDN rule is your frontline defense, but your origin should back it up. If WordPress fails to emit clear no-cache headers, an upstream reverse proxy or local caching layer like Varnish can still hang onto the response.
Open your Nginx site config (usually in /etc/nginx/sites-available/example.com) and look for your PHP block. Add a dedicated location block for admin-ajax.php that forces upstream proxies never to cache this output.
Drop this block right above your general location ~ .php$ block:
# Explicitly bypass caching for WordPress admin-ajax.php
location = /wp-admin/admin-ajax.php { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.2-fpm.sock; # Prevent intermediate proxies and browsers from caching add_header Cache-Control "no-cache, no-store, must-revalidate, max-age=0" always; add_header Pragma "no-cache" always; add_header Expires "0" always; # Disable FastCGI cache if you run fastcgi_cache on Nginx fastcgi_cache_bypass 1; fastcgi_no_cache 1;
}Test your config syntax and reload Nginx:
sudo nginx -t && sudo systemctl reload nginxThis creates a fail-safe. Even if an edge rule gets disabled or misconfigured during an update, your origin sends an explicit no-store directive that compliant proxies and browsers have to respect. Check the MDN Cache-Control documentation if you want a refresher on exact directive behavior.
Handling GET vs POST Requests on admin-ajax.php
Standard HTTP says proxies shouldn’t cache POST requests, and most don’t. The real problem is that tons of WordPress themes and plugins use GET requests on admin-ajax.php for read-only dynamic data—like live search predictions, modal contents, or cart counters.
That’s where edge caches bite you. If a script requests admin-ajax.php?action=get_cart_count&_nonce=abc123 and your CDN is set to ignore query strings, it caches that cart count and serves it to every other user.
If you’ve already configured your CDN to ignore tracking query strings like utm_source, make sure that rule never touches admin paths. admin-ajax.php needs every query parameter passed straight through to the origin, or better yet, completely bypassed regardless of whether it’s a GET or a POST.
The WordPress Plugin AJAX Handbook covers these patterns. Test both request methods whenever you audit your site’s network activity.
Testing and Validating the Edge Bypass
With the EdgeRule active and Nginx reloaded, test the endpoint from your terminal. Fire two identical cURL requests back-to-back with a dummy action.
First, test with GET:
curl -i "https://example.com/wp-admin/admin-ajax.php?action=woocommerce_get_refreshed_fragments"Check the headers. You’re watching x-cache and cache-control:
HTTP/2 200
date: Wed, 18 Oct 2023 14:35:45 GMT
content-type: application/json; charset=UTF-8
server: nginx
cache-control: no-cache, no-store, must-revalidate, max-age=0
pragma: no-cache
expires: 0
x-cache: BYPASSRun the exact same command immediately a second time. On a broken setup, request #2 comes back as x-cache: HIT. With your bypass rule working, it will return x-cache: BYPASS (or MISS if bypass routes through an uncached origin pass).
Now verify that POST requests behave the same way:
curl -i -X POST "https://example.com/wp-admin/admin-ajax.php" -d "action=woocommerce_get_refreshed_fragments"Both requests should bypass the edge cache and pull fresh nonces straight from PHP.
One quick note: while testing, make sure your server logs show actual visitor IPs instead of the CDN edge proxy IP. If they don’t, check how to restore real visitor IPs in Nginx with ngx_http_realip_module.
Frequently Asked Questions
Can I cache admin-ajax.php if I only use it for public data?
In theory, yes. In practice, don’t do it. Even for public data (like store locations), WordPress runs through full bootstrap and user session initialization on every request. If an admin triggers that endpoint first, admin nonces or user-specific session fragments can leak into the cached payload. If you need cacheable public data, use a custom endpoint on the WordPress REST API (/wp-json/) with explicit cache headers instead.
Why does StackPath still show x-cache: HIT after setting the rule?
Nine times out of ten, it’s rule priority. If you have an aggressive caching rule higher in your StackPath list (like caching all .php or matching /*), StackPath processes that first and serves the cached response. Drag your bypass rule to the top and run a full cache purge.
Does bypassing admin-ajax.php slow down my WordPress site?
No. Dynamic calls are supposed to reach your origin PHP process so cart items, user data, and database records stay accurate. Your CSS, JavaScript, fonts, images, and static HTML pages stay cached at the edge. You’re only punching a hole for requests that must never be stale.
Should I also bypass wp-admin entirely?
Yes, absolutely. Never cache /wp-admin/ or wp-login.php at the edge. Caching them leads to login loops, broken admin dashboards, and auth leaks. Standard practice is to bypass /wp-admin/*, /wp-login.php, and WooCommerce cart and checkout URLs.
Next Steps
Once admin-ajax.php is bypassing the edge cleanly, take a close look at your REST API routes. If your theme or frontend components fetch dynamic state via /wp-json/, apply this same audit to those endpoints so you only cache responses that are truly static.

