WordPress Performance FAQ
My host says that admin-ajax.php and WooCommerce requests like wc-ajax=get_refreshed_fragments are causing very high CPU load.
I understand that I should not simply block these requests because some of them may be required for cart, checkout, sessions, or plugin functionality.
But why can a single AJAX endpoint create so much CPU load in WordPress/WooCommerce? Is the problem really the AJAX file itself, or is each AJAX request triggering a much larger WordPress/plugin execution path behind the scenes?
Yes—the high CPU load is usually not caused by admin-ajax.php itself. The AJAX endpoint is often just the entry point. The real cost comes from everything WordPress and WooCommerce execute after that request arrives.
Think of it this way:
Browser
↓
admin-ajax.php
↓
Load WordPress
↓
Load active plugins
↓
Load theme
↓
Run AJAX callback
↓
Database queries
↓
Object cache/session/cart logic
↓
Generate response
Every AJAX request can effectively bootstrap a large portion of WordPress.
Why admin-ajax.php is expensive
When a request hits admin-ajax.php, WordPress typically:
- Loads core WordPress code.
- Loads all active plugins.
- Loads the active theme (or much of its functionality).
- Fires numerous hooks and filters.
- Executes the specific AJAX handler.
Even if the AJAX action itself is simple, the initialization overhead may already consume significant CPU and memory.
For example:
- 1 page view = 1 PHP request
- 1 page view + 5 AJAX calls = 6 PHP requests
If 100 concurrent visitors generate several AJAX requests each, PHP workers become busy very quickly.
Why WooCommerce AJAX is often worse
WooCommerce adds substantial processing on top of WordPress.
A request like:
?wc-ajax=get_refreshed_fragments
may trigger:
- Session loading
- Cart reconstruction
- Customer object creation
- Cart total calculations
- Shipping calculations (sometimes)
- Tax calculations
- Coupon validation
- Fragment generation (mini-cart HTML)
- Database reads/writes
The endpoint's purpose is usually to refresh cart information displayed in the header or mini-cart.
A simplified flow looks like:
get_refreshed_fragments
↓
Load WooCommerce session
↓
Load cart
↓
Calculate totals
↓
Render mini-cart template
↓
Return JSON
That is much heavier than a simple JSON API returning a few values.
Why CPU spikes become extreme
The biggest issue is often frequency, not individual cost.
Imagine:
- Product page loads
- Theme requests cart fragments
- Several plugins make their own AJAX calls
- Live search plugin polls
- Analytics plugin sends AJAX events
- Wishlist plugin refreshes state
One visitor can generate:
1 page request
+ 3 admin-ajax requests
+ 2 WooCommerce AJAX requests
+ 1 heartbeat request
------------------------
7 PHP executions
With 50 concurrent visitors:
50 visitors × 7 requests
= 350 PHP executions
Each execution may bootstrap WordPress and WooCommerce.
Why hosts often point at admin-ajax.php
Hosting dashboards commonly show the PHP script receiving the requests:
/wp-admin/admin-ajax.php
or
/?wc-ajax=get_refreshed_fragments
But that does not identify the underlying cause.
It's similar to saying:
"The front door is busy."
The door isn't the problem; it's what happens inside after people enter.
The real question is:
- Which AJAX actions are being called?
- How often?
- By which plugin, theme, or custom code?
- How expensive are the database queries inside those actions?
Common root causes
High CPU from these endpoints is often caused by:
1. Cart fragments on every page
Older WooCommerce setups refresh mini-cart fragments on every page view, even when visitors are not shopping.
This can generate a large amount of unnecessary AJAX traffic.
2. Heartbeat API abuse
WordPress Heartbeat uses admin-ajax.php repeatedly.
If many logged-in users are active:
admin-ajax.php?action=heartbeat
can become a major source of load.
3. Live search plugins
Search-as-you-type functionality may send an AJAX request on every keystroke.
4. Expensive custom code
A custom AJAX handler might execute:
- large
WP_Queryoperations - uncached database joins
- external API calls
- expensive WooCommerce product lookups
5. Bot traffic
Bots often trigger the same JavaScript and AJAX endpoints as real visitors.
A site receiving heavy bot traffic can end up processing thousands of WooCommerce AJAX requests.
6. Plugin conflicts
Multiple plugins may hook into cart updates or checkout events, causing one AJAX request to trigger unexpectedly large amounts of work.
How to investigate properly
Instead of blocking the endpoint, identify which action is expensive.
Useful things to measure:
- Top AJAX actions in logs
- Frequency of each action
- Slow query logs
- Plugin-level profiling
- Database query counts
For example, look for patterns like:
admin-ajax.php?action=heartbeat
admin-ajax.php?action=my_custom_search
admin-ajax.php?action=elementor_ajax
?wc-ajax=get_refreshed_fragments
?wc-ajax=update_order_review
The specific action is often more important than the endpoint itself.
The key takeaway
Your understanding is correct:
admin-ajax.phpandwc-ajaxendpoints are usually not the true source of the CPU load. They are dispatchers that bootstrap WordPress/WooCommerce and then execute whatever plugin, theme, cart, session, database, and business logic is attached to that request.
A single AJAX endpoint can therefore generate surprisingly high CPU usage because every request may involve loading much of WordPress and WooCommerce, running dozens of hooks, performing database queries, rebuilding carts, and rendering templates before returning a response. The endpoint is simply where all that work enters the application.
If admin-ajax.php and wc-ajax are only dispatchers, and each request can bootstrap WordPress, WooCommerce, active plugins, hooks, sessions, cart logic, and database work, then is reducing the frequency of AJAX calls only one part of the solution?
For unavoidable AJAX requests such as cart fragments, checkout updates, or session-related WooCommerce calls, shouldn't the deeper question be: why does every small dynamic request have to carry the weight of the full WordPress/plugin runtime?
In other words, if these requests cannot simply be blocked and cannot always be cached, would reducing the execution scope of each AJAX request be a more direct strategy than only throttling, caching, or optimizing the callback after the full stack has already loaded?
Yes. In fact, once you've eliminated obviously excessive AJAX traffic, execution scope becomes the more interesting architectural question.
There are really two separate multipliers in play:
Total CPU Load
=
Request Frequency
×
Cost Per Request
Most hosting advice focuses on the first multiplier:
- reduce AJAX frequency
- disable unnecessary polling
- cache what can be cached
- stop cart fragments on non-commerce pages
Those measures often work because they immediately reduce the number of PHP executions.
But for requests that are genuinely required—cart updates, checkout recalculations, session state, inventory checks—the second multiplier becomes critical:
Why does a request that only needs a few pieces of data incur the cost of loading most of WordPress and WooCommerce?
That's a valid architectural criticism of the traditional WordPress execution model.
The fundamental design issue
Classic WordPress was designed around page generation, not lightweight APIs.
Historically the model was:
Request
↓
Load WordPress
↓
Load plugins
↓
Load theme
↓
Run hooks
↓
Generate page
AJAX was added later.
Instead of creating a separate lightweight runtime, WordPress largely reuses the same bootstrap process:
AJAX Request
↓
Load WordPress
↓
Load plugins
↓
Run hooks
↓
Execute AJAX callback
The advantage is compatibility.
The disadvantage is overhead.
Every plugin can assume WordPress is fully loaded.
Every AJAX handler gets access to:
- users
- capabilities
- options
- post types
- taxonomies
- WooCommerce APIs
- sessions
- hooks
- plugin integrations
That convenience has a cost.
WooCommerce amplifies the effect
WooCommerce is not just a shopping cart.
It is a large application layer on top of WordPress.
When WooCommerce initializes, many components become available:
Customer
Cart
Session
Coupons
Taxes
Shipping
Payments
Inventory
Extensions
A checkout-related AJAX request may legitimately require several of those systems.
For example:
Update shipping method
sounds small.
But it may require:
Load customer
Load session
Load cart
Recalculate totals
Apply coupons
Calculate taxes
Calculate shipping
Trigger extension hooks
Return updated totals
The actual business operation is not necessarily small.
Why "just load less WordPress" is difficult
Conceptually, it sounds attractive:
AJAX request
↓
Load only 10% of WordPress
↓
Run callback
The problem is dependency chains.
A plugin might do:
WC()->cart->calculate_totals();
That function may depend on:
- session state
- customer data
- tax configuration
- shipping zones
- coupon logic
- dozens of hooks
Removing parts of the bootstrap can break assumptions throughout the ecosystem.
WordPress prioritizes compatibility over minimal runtime.
Modern systems often solve this differently
Many modern architectures separate concerns:
Frontend
↓
API Gateway
↓
Dedicated Cart Service
or
Frontend
↓
REST Endpoint
↓
Focused Business Logic
Instead of loading an entire CMS runtime.
In such systems:
Get cart count
might execute:
Read session
Read cart
Return JSON
and nothing else.
The execution scope is much smaller.
This is why the REST API sometimes performs better
WordPress's REST API can be somewhat more efficient than admin-ajax.php in some situations.
Not because WordPress suddenly becomes lightweight, but because:
- endpoints are more structured
- routing is cleaner
- developers are encouraged to build focused handlers
- some admin-specific loading can be avoided
However:
A WooCommerce REST endpoint that calls the same cart-calculation code may still end up consuming similar CPU.
The business logic often dominates the cost.
The most effective optimization hierarchy
For unavoidable WooCommerce requests, the usual order is:
1. Eliminate unnecessary requests
Biggest wins often come from:
- disabling unused fragment refreshes
- reducing polling
- removing plugins generating AJAX traffic
2. Reduce work inside the request
Examples:
- optimize database queries
- reduce hooks
- avoid repeated calculations
- add object caching
- cache expensive lookups
3. Reduce bootstrap overhead
Harder, but sometimes possible:
- custom REST endpoints
- lightweight custom endpoints
- selective plugin loading
- avoiding WooCommerce initialization where unnecessary
4. Architectural redesign
Largest potential gain, largest effort:
- headless commerce
- dedicated cart services
- external session stores
- decoupled APIs
The subtle but important point
Hosts often report:
admin-ajax.php = 80% CPU
which leads site owners to think:
AJAX is the problem.
More accurately:
AJAX frequency × WordPress bootstrap × WooCommerce business logic
= CPU load
The endpoint is merely where those factors converge.
So your intuition is correct: for requests that are truly necessary and cannot be cached, reducing the execution scope and dependency footprint of the request is often a more fundamental optimization than merely reducing request frequency. The challenge is that WordPress and WooCommerce were designed around a highly integrated runtime, making execution-scope reduction significantly harder than it sounds. That's why most optimization efforts focus first on eliminating requests and optimizing callbacks before attempting deeper architectural changes.
[[Mike]]
That makes sense, but does reducing AJAX execution scope necessarily require moving to headless commerce, external cart services, or custom lightweight endpoints outside the normal WordPress/WooCommerce runtime?
Or could the same prevention-oriented principle be applied inside WordPress by classifying AJAX requests early?
For example, a wc-ajax=get_refreshed_fragments request has a much narrower purpose than a full product page, checkout page, or admin request. If the request type is known before the full plugin stack is loaded, could a WordPress-native system apply a reduced execution profile for that AJAX context?
In other words: instead of treating every AJAX request as “load WordPress, load WooCommerce, load all active plugins, then run the callback,” could the better model be:
identify the AJAX context early → load only the required runtime for that context → prevent unrelated PHP/plugin execution before it begins?
You're describing something that many performance engineers eventually arrive at:
The biggest optimization is not making code run faster. It's preventing unnecessary code from running at all.
And yes, in principle, your proposed model is entirely possible inside WordPress, without going headless.
The challenge is not technical feasibility. The challenge is compatibility.
The architecture you're proposing
Today, WordPress effectively behaves like:
Request arrives
↓
Load WordPress
↓
Load all active plugins
↓
Initialize environment
↓
Determine request purpose
↓
Execute callback
You're suggesting:
Request arrives
↓
Determine request purpose
↓
Load only required components
↓
Execute callback
From a systems-design perspective, that's generally superior.
It follows the same principle used in:
- microservices
- serverless functions
- API gateways
- event-driven architectures
The earlier you classify the request, the more work you can avoid.
Why WordPress doesn't do this by default
The problem is that WordPress plugins are not written with strict dependency declarations.
Imagine WooCommerce receives:
?wc-ajax=get_refreshed_fragments
At first glance you might think:
Need:
✓ WooCommerce cart
✓ Session
✓ Customer
Don't need:
✗ SEO plugin
✗ Form plugin
✗ Analytics plugin
✗ Gallery plugin
But WordPress has no reliable way to know that.
A plugin author may have written:
add_action(
'woocommerce_before_calculate_totals',
'my_plugin_logic'
);
or
add_filter(
'woocommerce_add_to_cart_fragments',
'modify_fragments'
);
Those hooks may affect the AJAX response.
If that plugin isn't loaded, behavior changes.
So WordPress takes the safe approach:
Load everything
because it guarantees compatibility.
The key insight: request classification can happen early
Technically, WordPress already knows quite a bit very early.
For example:
/wp-admin/admin-ajax.php?action=my_action
reveals:
action=my_action
before most business logic runs.
Likewise:
?wc-ajax=get_refreshed_fragments
reveals:
wc-ajax=get_refreshed_fragments
immediately.
So from a purely architectural perspective:
Request type
↓
Execution profile
↓
Selective loading
is absolutely possible.
In fact, some systems already do partial versions of this
Several performance-oriented approaches already attempt this idea.
Conditional plugin loading
Some optimization systems effectively do:
Product page
→ load plugins A B C
Checkout page
→ load plugins A B C D E
AJAX request
→ load plugins A only
This can significantly reduce runtime.
The limitation is that the site owner must know which plugins are safe to exclude.
MU-plugin bootstrap filters
Advanced developers sometimes intercept requests extremely early and decide:
This is a heartbeat request
or
This is a specific WooCommerce AJAX action
then selectively disable functionality.
Again, this can work well but requires careful dependency analysis.
WooCommerce itself already uses context flags
WooCommerce knows when it's handling:
frontend
admin
REST API
AJAX
checkout
cron
and avoids certain operations in some contexts.
So the concept already exists.
The issue is that the ecosystem is not built around strict runtime profiles.
The deeper obstacle is hook-based architecture
The biggest reason your model is difficult is not WordPress itself.
It's the hook system.
Consider:
do_action('woocommerce_before_calculate_totals');
WooCommerce has no idea which plugins might be attached.
Potentially:
Inventory plugin
Subscription plugin
Membership plugin
Pricing plugin
Rewards plugin
ERP integration
Tax plugin
Shipping plugin
could all be listening.
Therefore:
Load WooCommerce
often implies:
Load every plugin that might hook into WooCommerce
which quickly becomes:
Load almost everything
A more modern WordPress could theoretically support this
If WordPress had a dependency-aware execution model, plugins could declare something like:
Needed for:
✓ frontend pages
✓ checkout
✗ cart fragments
✗ heartbeat
✗ REST inventory lookups
Then the runtime could build a profile:
get_refreshed_fragments
↓
Load:
WooCommerce Core
Session Module
Cart Module
Skip:
SEO
Forms
LMS
Analytics
Gallery
That would be a substantial architectural improvement.
But it would require ecosystem-wide conventions that WordPress currently lacks.
Applied specifically to get_refreshed_fragments
Your example is particularly interesting because many stores no longer need the historical implementation.
The request's purpose is narrow:
Return updated mini-cart fragments
In theory, a runtime could load:
Session
Customer
Cart
Fragment renderer
and little else.
The question is whether some plugin has attached logic to that process.
WordPress assumes the answer may be "yes."
Therefore it loads the full environment.
So is your model valid?
Yes.
In fact, from a software architecture perspective, your model is closer to how high-performance systems are typically designed:
Classify request first
↓
Load only required subsystems
↓
Execute business logic
↓
Return response
rather than:
Load entire application
↓
Figure out request purpose
↓
Execute business logic
The reason WordPress and WooCommerce do not fully implement this isn't that it's impossible or even undesirable. It's that the platform's plugin ecosystem is built on the assumption that any request may need access to any hook, filter, or extension. Compatibility became the primary design goal, and compatibility naturally pushes the runtime toward "load everything first."
So you've identified a genuine architectural tension: performance favors early classification and selective loading, while extensibility favors loading a complete runtime where every plugin can participate in every request. WordPress historically chose extensibility. The CPU cost you're seeing is one of the consequences of that choice.
This is exactly where LiteCache Rush applies its principle of Performance by Prevention. High CPU load from admin-ajax.php or wc-ajax=get_refreshed_fragments is not caused by the endpoint name itself, but by the WordPress, WooCommerce, plugin, hook, session, cart, and database work triggered behind it. Traditional optimization tries to reduce request frequency or make the callback cheaper. Rush targets the earlier layer: identify the AJAX/request context before the full plugin stack is loaded and prevent unrelated PHP and plugin execution from entering that request at all. For unavoidable dynamic WooCommerce and AJAX requests, this turns the question from “How do we make this heavy request faster?” into “Why should unrelated application code run for this request in the first place?”