WordPress Performance FAQ


Mike

Why does my server CPU spike when many users hit dynamic WordPress pages that cannot be served from page cache?

For cached public pages, WordPress may not run at all. But for dynamic pages such as checkout, account pages, search results, AJAX requests, REST endpoints, or personalized sessions, every request still has to execute PHP, WordPress, active plugins, hooks, database access, and application logic.

Is the CPU spike mainly caused by many users triggering real WordPress execution at the same time, instead of being served from cached HTML?

Sam

Yes. In many WordPress environments, the primary reason for CPU spikes during traffic surges is exactly that: requests are bypassing page cache and forcing full WordPress execution for every user.

Here's what happens.

Cached page request

For a cacheable public page:

  1. Web server receives the request.
  2. Cache layer (e.g., Nginx FastCGI Cache, Varnish, Cloudflare cache, WP Rocket page cache) finds a stored HTML page.
  3. Cached HTML is returned immediately.

In the ideal case:

  • PHP never runs.
  • WordPress never loads.
  • Plugins never execute.
  • Database queries are avoided.

CPU cost per request is very low.


Dynamic page request

For a non-cacheable page such as:

  • WooCommerce checkout
  • Cart
  • My Account
  • Logged-in user pages
  • Search results
  • AJAX requests (admin-ajax.php)
  • REST API endpoints
  • Personalized dashboards
  • Membership content

every request typically follows this path:

  1. Request reaches PHP.
  2. WordPress bootstrap loads.
  3. Active plugins load.
  4. Themes load.
  5. Hooks and filters execute.
  6. Authentication/session logic runs.
  7. Database queries execute.
  8. Business logic executes.
  9. HTML or JSON is generated.
  10. Response is sent.

This entire stack consumes CPU time on every request.


Why CPU rises nonlinearly

Suppose:

  • One checkout request takes 150 ms of CPU time.
  • One search request takes 300 ms of CPU time.

If 100 users trigger those requests simultaneously:

  • PHP workers become busy.
  • Database queries increase.
  • Context switching increases.
  • Queues form behind PHP-FPM workers.
  • CPU utilization can rapidly approach 100%.

The server is no longer serving static files; it is actively computing responses for every user.


What often makes it even worse

The WordPress core bootstrap is only part of the cost.

Frequently the largest CPU consumers are:

  • WooCommerce calculations
  • Membership plugins
  • Page builders
  • Search plugins
  • Heavy REST endpoints
  • External API calls
  • Expensive database queries
  • Poorly optimized hooks running on every request

For example, a custom plugin that executes several complex database queries during checkout can consume far more CPU than WordPress itself.


A useful mental model

Think of page cache as serving a pre-rendered PDF.

Without cache, WordPress must regenerate the document from scratch for every visitor.

If 1,000 visitors request the cached page:

  • The server mostly reads files from memory/disk.

If 1,000 visitors request dynamic pages:

  • The server performs 1,000 separate PHP executions.
  • Plugins and database logic run 1,000 times.

That difference is often the source of dramatic CPU spikes.

So the short answer

Yes. When many users hit pages that cannot be served from page cache, the CPU spike is largely caused by concurrent execution of PHP, WordPress core, plugins, hooks, database access, and application logic for each request. Cached HTML responses avoid most of that work, while dynamic requests force the application stack to run repeatedly and simultaneously.

Mike

That makes sense. So the CPU spike is not caused merely by visitor count, but by many users triggering full dynamic WordPress execution at the same time.

But shouldn't we also look at the weight of each individual dynamic request?

If every checkout, account, search, AJAX, or REST request loads WordPress core, the theme, all active plugins, hooks, services, database access, and application logic, then even small unnecessary overhead gets multiplied across every concurrent request.

Would reducing the execution scope of each dynamic request - so unrelated plugins, hooks, services, and PHP code do not enter the request lifecycle in the first place - reduce CPU pressure more directly than only adding more CPU, more PHP workers, Redis, or database tuning?

Sam

Yes. In fact, once you've already implemented reasonable caching, reducing the cost per dynamic request is often the most effective way to reduce CPU pressure.

A useful way to think about it is:

Total CPU consumption ≈ request rate × cost per request

So if:

  • 100 concurrent dynamic requests each consume 200 ms of CPU time,
  • that's roughly twice the CPU work of
  • 100 concurrent requests consuming 100 ms each.

Reducing the work done inside every request scales across all traffic.


Why optimization inside the request lifecycle is so powerful

Many WordPress sites focus on:

  • adding more CPU cores,
  • increasing PHP-FPM workers,
  • adding Redis,
  • tuning MySQL,
  • upgrading servers.

Those can help, but they mostly increase capacity.

If each request still loads a large amount of unnecessary code, you've simply made a bigger engine to perform the same wasted work.

Consider a checkout request:

Required:

  • WooCommerce checkout logic
  • payment gateway
  • cart/session handling
  • order validation

Possibly unnecessary:

  • page builder hooks
  • analytics plugins
  • SEO plugins running expensive filters
  • unrelated custom services
  • plugins registering REST routes that are never used
  • heavy admin functionality loaded on frontend requests

If those components execute during every checkout request, their cost is multiplied by every concurrent user.


Multiplication effect under load

Imagine:

Scenario A

Checkout request cost:

  • Core WordPress: 40 ms
  • WooCommerce: 60 ms
  • Required business logic: 50 ms

Total:

  • 150 ms/request

100 simultaneous requests:

  • 15,000 ms of CPU work

Scenario B

Same checkout plus unnecessary code:

  • Core WordPress: 40 ms
  • WooCommerce: 60 ms
  • Required business logic: 50 ms
  • Unused plugin hooks: 30 ms
  • Extra service initialization: 40 ms
  • Miscellaneous overhead: 20 ms

Total:

  • 240 ms/request

100 simultaneous requests:

  • 24,000 ms of CPU work

Nothing changed about traffic.

The server is simply doing 60% more work per request.


Why "don't load it" is often better than "load it faster"

A common optimization hierarchy is:

  1. Don't execute code.
  2. Execute less code.
  3. Execute code more efficiently.
  4. Add hardware.

The biggest savings usually come from the first two.

For example:

  • Preventing a plugin from loading on checkout can eliminate its entire overhead.
  • Preventing a hook from firing removes its cost completely.
  • Avoiding a database query is better than making the query 10% faster.

Zero execution beats optimized execution.


Common high-impact areas

For dynamic WordPress traffic, CPU usage is frequently reduced by:

Conditional plugin loading

Only load functionality where needed.

Examples:

  • Form plugins only on pages with forms.
  • LMS functionality only on course pages.
  • Marketing integrations only where required.

Reducing hook execution

Many plugins attach callbacks to:

  • init
  • wp
  • template_redirect
  • wp_head
  • REST hooks
  • AJAX hooks

These run on enormous numbers of requests.

Removing unnecessary callbacks can have a measurable effect.

Avoiding unnecessary service bootstrapping

Many modern plugins instantiate:

  • dependency injection containers
  • API clients
  • logging systems
  • analytics services

even when the request never uses them.

Lazy-loading can reduce overhead.

Optimizing AJAX and REST endpoints

AJAX and REST endpoints often become hotspots because they bypass page cache.

A poorly optimized endpoint hit thousands of times per minute can dominate CPU consumption.

Reducing database work

Even if Redis is present, expensive queries and excessive object creation still consume CPU.

Database optimization helps, but reducing the number of queries is often better.


Where Redis and hardware fit

Redis, database tuning, and additional CPU are still valuable.

However, they address different bottlenecks:

Approach Effect
Remove unnecessary execution Reduces work
Reduce plugin loading Reduces work
Remove hooks Reduces work
Optimize code paths Reduces work
Redis/Object Cache Makes some operations cheaper
Database tuning Makes queries faster
More PHP workers Handles more simultaneous requests
More CPU cores Increases available processing capacity

The most scalable strategy is usually:

  1. Eliminate unnecessary work.
  2. Optimize necessary work.
  3. Cache what can be cached.
  4. Then add hardware if demand still exceeds capacity.

So your intuition is correct: if dynamic requests are the source of CPU spikes, reducing what actually enters the WordPress request lifecycle can often have a larger impact than simply adding resources. Lowering the execution cost of each request reduces CPU consumption across every concurrent user and frequently improves response times at the same time.

Mike

That makes sense. But for true CPU reduction, isn't the timing of the decision critical?

If WordPress has already loaded the active plugin stack, registered hooks, initialized services, read options, and created objects, then later deciding that a plugin is not needed can still reduce some behavior, but it cannot undo the bootstrap and participation cost that already happened.

So when we say “prevent unnecessary execution,” shouldn't we distinguish between:

  1. loaded runtime that is optimized or made conditional later,
  2. and unnecessary runtime that never enters the request lifecycle at all?

For dynamic requests that cannot be page-cached, would the stronger model be: classify the request early, before the full plugin stack is loaded, and then allow only the runtime needed for that request to participate?

Sam

Yes. From a systems-performance perspective, that distinction is extremely important.

There is a significant difference between:

  • code that loads and then decides not to do much, and
  • code that never loads at all.

Those are often discussed together as "optimization," but they affect different parts of the cost curve.

Three levels of optimization

Level 1: Conditional behavior after loading

A plugin loads, registers hooks, initializes classes, reads configuration, and then later determines:

"This request doesn't need my functionality."

Example:

add_action('init', function () {
    if (!is_checkout()) {
        return;
    }

    // checkout logic
});

The checkout logic is skipped, which helps.

However, the plugin file was still loaded, PHP parsed it, classes may have been autoloaded, hooks registered, options read, and initialization code executed.

You reduced work, but not the cost of participation.


Level 2: Conditional bootstrap

A plugin loads but delays expensive initialization.

Example:

if (is_checkout()) {
    $checkout_service->boot();
}

This is better because fewer objects and services are created.

But the plugin still entered the request lifecycle.

The request still paid some bootstrap cost.


Level 3: Never entering the lifecycle

The strongest optimization is:

This request never loads the unnecessary runtime in the first place.

For example:

  • Search request loads search-related runtime.
  • Checkout request loads commerce runtime.
  • REST endpoint loads only endpoint dependencies.
  • Account request loads account-related functionality.

Everything else is excluded before bootstrap.

At that point there is no:

  • file include cost,
  • autoloader activity,
  • object construction,
  • hook registration,
  • option retrieval,
  • service initialization,

because the runtime never participated.

This is qualitatively different from optimization that happens later.


Why timing matters

CPU consumption accumulates from the moment PHP begins building the application environment.

By the time WordPress reaches many common hooks:

  • init
  • wp
  • template_redirect
  • wp_loaded

a substantial amount of work has already occurred.

The request has often already:

  • loaded active plugins,
  • executed plugin bootstrap code,
  • built hook registries,
  • instantiated service containers,
  • loaded configuration,
  • performed database reads.

At that point, deciding not to execute additional logic is useful, but much of the participation cost has already been paid.


Dynamic requests amplify bootstrap cost

This becomes especially important for non-cacheable traffic.

Imagine:

  • 1,000 checkout requests/hour
  • 10,000 REST requests/hour
  • 50,000 AJAX requests/hour

If every request loads:

  • 40 plugins
  • thousands of registered hooks
  • hundreds of service objects

before deciding what is actually needed, then bootstrap itself becomes a major CPU consumer.

Even if each plugin does very little afterward.


Thinking in terms of runtime participation

A useful mental model is:

Every component has two costs:

  1. Participation cost

    • loading files
    • registration
    • initialization
    • dependency wiring
  2. Execution cost

    • actual business logic
    • queries
    • calculations
    • API calls

Most optimization discussions focus on execution cost.

But on large WordPress installations, participation cost can become substantial because it is paid on every dynamic request.


Your proposed model

What you're describing is closer to how high-performance application platforms are often designed:

  1. Classify request as early as possible.
  2. Determine required capabilities.
  3. Load only those capabilities.
  4. Exclude unrelated runtime from the request entirely.

Conceptually:

Incoming request
       ↓
Request classification
       ↓
Required runtime set
       ↓
Selective bootstrap
       ↓
Execute request

instead of:

Incoming request
       ↓
Load everything
       ↓
Initialize everything
       ↓
Determine what is needed
       ↓
Skip some logic

The first model generally scales better because the amount of work grows with the needs of the request rather than the total size of the application.


The practical WordPress reality

Traditional WordPress was not originally designed around selective runtime composition.

Its default architecture assumes:

  • load WordPress,
  • load active plugins,
  • register hooks,
  • then dispatch behavior.

So achieving true request-scoped loading often requires deeper architectural changes than simply adding conditionals inside plugins.

That's why many WordPress performance optimizations eventually hit diminishing returns: they optimize behavior after bootstrap, while a significant portion of the cost comes from bootstrap itself.

So yes—the strongest CPU-reduction model is not merely "run less code after loading." It is "prevent unnecessary runtime from participating in the request lifecycle at all," and the earlier that decision is made, the more bootstrap cost is avoided for every dynamic request.

Mike

This is exactly where LiteCache Rush applies its principle of Performance by Prevention. Dynamic WordPress requests that cannot be served from page cache still have to execute, but they do not necessarily need the full WordPress/plugin runtime. Traditional optimization often reduces execution cost after plugins have already loaded. Rush targets the earlier participation cost: it identifies the request context before the full plugin stack is loaded and prevents unrelated PHP, plugins, hooks, services, object creation, option reads, and initialization work from entering the request lifecycle in the first place. For high-CPU dynamic traffic, the strongest optimization is not merely running code faster - it is preventing unnecessary runtime from participating at all.