A WordPress site pegged at 100% CPU is usually not a hardware problem. Almost always, it is a software pattern — a misbehaving plugin, a brute-force attack, unoptimised database queries, or a cron job running on every page load — and the fix is specific to the cause rather than a general “upgrade the server” response.

This guide walks through the real causes of high CPU on WordPress sites, how to diagnose each, and the specific fix for each pattern. It applies whether you are on shared hosting watching your resource meter turn red, on a VPS seeing top show PHP at 100%, or on dedicated hardware seeing load averages that shouldn’t make sense.

First — verify it’s actually your CPU

Before diagnosing WordPress, confirm the CPU usage is real and belongs to you. On shared hosting, the “CPU usage” number in a control panel sometimes reflects neighbouring-account activity rather than yours. Three ways to verify:

  • Check actual processes. SSH into the server (if available) and run top or htop. If PHP-FPM or Apache processes show high CPU, the problem is your workload. If top shows other processes dominating, the problem is elsewhere.
  • Compare load averages. A load average consistently above your core count means the CPU is genuinely saturated. A load below core count with your control panel claiming high usage is probably container-level throttling masquerading as a CPU problem.
  • Check the CPU time accounting in control panels. cPanel, Enhance CP, Plesk, and similar panels have a “CPU usage over time” graph. Real CPU problems show sustained high usage over hours; false alarms spike briefly and disappear.

If the diagnosis is genuine CPU saturation, the next step is identifying which workload is causing it.

Diagnostic sequence

Run these checks in order. Most high-CPU problems are identified within 15 minutes of starting.

Step 1: What’s consuming CPU right now?

SSH to the server and run:

top -b -n 1 | head -20

Or the interactive version:

htop

PHP processes (php-fpm, php-cgi, or httpd with mod_php) at the top of the list means WordPress. MySQL/MariaDB at the top means database. Multiple of each usually means WordPress running inefficient queries.

Step 2: How many PHP workers are running?

ps aux | grep php-fpm | wc -l

If this is close to your pm.max_children setting, your worker pool is saturated — every worker is busy and new requests are queueing. The root cause is further down, but saturation itself is the symptom that surfaces as “slow site” for visitors.

Step 3: What are those workers doing?

ps auxf | grep php-fpm

With f for forest view, you see the parent processes and child worker processes. Each worker is typically handling a specific request — and there is often a pattern. All workers on admin-ajax.php? Heartbeat or cart-fragments issue. All on wp-login.php? Brute force. All on a specific plugin endpoint? That plugin.

Step 4: What does the access log say?

tail -1000 /var/log/apache2/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head

This counts the most-requested URLs in the last 1,000 requests. Healthy traffic shows a normal distribution of page URLs. Problem traffic shows abnormal concentration — e.g. 800 out of 1,000 requests to /xmlrpc.php or /wp-login.php is clearly attack traffic, not legitimate users.

Step 5: Query Monitor on an affected page

On the WordPress side, install Query Monitor temporarily. Load the slowest page while logged in. The “Queries” tab shows query count and total time; the “Queries by Component” tab attributes query cost to individual plugins. 800+ queries per page is a problem; 1,500+ is a crisis.

The common causes and fixes

Each section below is a specific pattern, how to confirm it, and the fix.

Cause 1: Bot traffic on wp-login.php or xmlrpc.php

Signal: Access log shows many requests to /wp-login.php, /xmlrpc.php, or /wp-admin/ from a small number of IPs or IP ranges.

Why it kills CPU: Every login attempt runs password hashing, which is deliberately slow (50-100ms of CPU per attempt). A thousand attempts per minute consumes significant CPU even if all fail.

Fix:

  • Block at the server level. Rate-limit /wp-login.php and /xmlrpc.php via your webserver or firewall before the request reaches PHP. On LiteSpeed or Apache:
<Files "wp-login.php">
  <RequireAll>
    Require all granted
    Require not env loginattempts
  </RequireAll>
</Files>
  • Install a plugin like Limit Login Attempts Reloaded — reduces damage but not as effective as server-level rate limiting because PHP still runs for each attempt.
  • Disable xmlrpc.php entirely if you don’t use the XML-RPC API. Most modern WordPress sites don’t. Add to .htaccess:
<Files xmlrpc.php>
  Order deny,allow
  Deny from all
</Files>
  • Move wp-login.php to a non-default URL with WPS Hide Login. Reduces automated attack volume dramatically.

Cause 2: admin-ajax.php flooding

Signal: Access log shows high volume of requests to /wp-admin/admin-ajax.php from normal visitor IPs (not an attack).

Why it kills CPU: Plugins use admin-ajax for background operations. WordPress Heartbeat alone fires every 15-60 seconds per open tab. A logged-in admin user with 3 tabs open hits admin-ajax 10-12 times per minute just from Heartbeat, each executing PHP. Add cart-fragments, analytics pings, chat widgets, and a busy site can generate thousands of admin-ajax calls per minute.

Fix:

  • Disable WP Heartbeat on frontend (usually unnecessary) and extend interval on admin. Install “Heartbeat Control” plugin, set frontend to “Disable”, admin to “Slow” (60-second interval).
  • Disable cart fragments on non-shop pages for WooCommerce. This single change drops admin-ajax load significantly on content-heavy WooCommerce sites. Filter:
add_action( 'wp_enqueue_scripts', function() {
  if ( ! is_woocommerce() && ! is_cart() && ! is_checkout() ) {
    wp_dequeue_script( 'wc-cart-fragments' );
  }
}, 11 );
  • Audit plugins using admin-ajax. Check the Network tab in browser DevTools while using the site — every admin-ajax request shows the action name (action=...). Identify which plugins are most active and assess whether you actually need them.

Cause 3: WP-Cron running on every page load

Signal: Page loads occasionally take 10-30 seconds unexpectedly. wp cron event list shows many scheduled events. Server CPU spikes periodically throughout the day.

Why it kills CPU: WordPress’s default behaviour is to check for due cron events on every page load. Visitors occasionally “pay” for running the cron by experiencing a slow page. On busy sites, this runs much more often than needed; on slow sites, it fires rarely and tasks can build up.

Fix:

  • Disable WordPress’s built-in cron. Add to wp-config.php:
define('DISABLE_WP_CRON', true);
  • Add a real cron job on the server running every 5-15 minutes:
*/5 * * * * wget -q -O - https://yoursite.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

Or use WP-CLI:

*/5 * * * * cd /path/to/wordpress && wp cron event run --due-now >/dev/null 2>&1

Effect: cron runs on a predictable schedule instead of unpredictably during user requests. Visitors never wait for cron. Admin and visitors see consistently fast page loads.

Cause 4: Missing object cache (database hammer)

Signal: Query Monitor shows 500+ queries per page, no “Object Cache” entry in the tab. MySQL CPU consistently high in top.

Why it kills CPU: Without a persistent object cache, every request re-queries data that should be served from memory — plugin options, user meta, repeated queries. MySQL does the work, consuming CPU on the database side and keeping PHP processes waiting.

Fix:

  • Install Redis or Memcached. On managed hosting, Redis is typically available at the server level with a one-click plugin activation. See our Redis for WooCommerce guide for detailed configuration.
  • Verify it’s active via Site Health → Info → Drop-ins (look for object-cache.php).
  • Monitor the hit ratio with redis-cli INFO stats — aim for 90%+.

Typical outcome: database CPU drops 40-60%, PHP CPU drops 20-30% because workers spend less time waiting on database responses.

Cause 5: Heavy or poorly-written plugins

Signal: Query Monitor’s “Queries by Component” tab shows one or two plugins dominating query count or execution time. Removing or deactivating the plugin drops CPU measurably.

Why it kills CPU: Plugin code quality varies enormously. Some plugins run dozens of queries on every page load. Some load entire database tables into PHP memory. Some make synchronous external API calls — common with AI plugins that block the worker.

Fix:

  • Identify the offenders. Query Monitor makes this easy. The plugin using 40% of query time on every page is the target.
  • Replace or remove. Often there’s a lighter alternative. A contact form plugin that runs 50 queries per page is replaceable with one that runs 3.
  • Disable synchronous external API calls on frontend page loads. Analytics pings to external services should fire asynchronously via JavaScript, not synchronously via PHP.
  • Profile the plugin if it’s critical. Sometimes it has caching or performance settings that aren’t enabled by default.

Cause 6: Theme or custom code inefficiency

Signal: Query Monitor attributes queries to the theme rather than a plugin. Pages using the theme are slow; pages that bypass theme rendering (admin-ajax responses, REST API) are fast.

Why it kills CPU: Themes sometimes include inefficient code — nested loops that hit the database inside, queries inside the_content() filters, or external API calls in the footer.

Fix:

  • Profile with Query Monitor — find specific slow queries attributed to theme files.
  • Look for loops within loops — a product listing that runs a query for each product’s category, which each run a query for each category’s term meta, etc.
  • Cache expensive computations. If the theme computes something on every page load that changes rarely, use set_transient() or wp_cache_set() to cache the result.

Cause 7: Bot traffic from search engines or scrapers

Signal: Access log shows high traffic from user agents like Googlebot, Bingbot, AhrefsBot, SemrushBot, or unidentified scrapers.

Why it kills CPU: Bots don’t respect rate limits and don’t benefit from client-side caching. A scraper hitting your product catalogue at 10 requests per second can consume more CPU than all your human visitors combined.

Fix:

  • Legitimate search bots (Googlebot, Bingbot): configure crawl rate in Google Search Console and Bing Webmaster Tools if they’re over-crawling.
  • SEO scraping bots (Ahrefs, SEMrush, Majestic, etc.): block in robots.txt or at the webserver level if you don’t use them. They account for significant traffic on many sites with no SEO benefit.
  • Malicious scrapers: block by user-agent pattern or IP range. Cloudflare, QUIC.cloud, and similar CDNs handle this at the edge, keeping the traffic from ever reaching your server.

A systematic approach

When a site hits high CPU and you’re trying to identify the cause fast, run this sequence:

  1. top or htop — is it PHP, MySQL, or other?
  2. tail -1000 access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head — any abnormal request patterns?
  3. ps auxf | grep php-fpm — what are the workers doing?
  4. Query Monitor on the slowest page — which component is expensive?
  5. wp cron event list — any stuck or overly-frequent cron events?
  6. redis-cli INFO stats (if Redis is installed) — healthy hit ratio?

Five minutes each, 30 minutes total. One of these almost always surfaces the cause.

When the cause is hosting, not WordPress

Some high-CPU situations are not WordPress problems — they are hosting problems:

  • CPU limits too low for your actual traffic. A site with 200 concurrent visitors on a plan sized for 20 will appear to have high CPU even with perfect configuration. The fix is hosting sized for reality.
  • Shared hosting noisy neighbours — your CPU appears high because other accounts on the same server are consuming resources. The fix is moving to isolated resources.
  • Outdated server software — PHP 7.4 uses significantly more CPU for the same work as PHP 8.3. Upgrading PHP is worth 20-30% CPU reduction for free. See our PHP 8.5 upgrade guide.
  • No OPcache — the PHP bytecode cache. Without OPcache, PHP recompiles every script on every request, massively increasing CPU usage. Confirm OPcache is enabled via phpinfo() or wp cli info.

If application-level fixes have been applied and CPU is still saturated under normal traffic, the hosting is undersized for the workload — which is a different conversation from “the WordPress site is broken”. For the full picture of how hosting affects performance, see our WordPress performance bottlenecks and server response time foundation guides.