How to Find Which Plugin Slows Down Your WordPress Site

How to Find Which Plugin Slows Down Your WordPress Site

To find the slow WordPress plugin, you stop guessing and start measuring: capture a baseline request, then bisect the plugin set until the response time collapses. The tool that makes this practical is a profiler that reports per plugin, per hook, and per query time, because a plugin's cost is rarely one thing. It is the sum of every callback it registers on init, wp_loaded, template_redirect, and the REST or admin-ajax paths your page actually touches.

Before you disable anything, get a number you can trust. A single page load is noisy, so measure many of them and look at the distribution, not the average. From a terminal on the same network as the site, or on the server itself, you can time the raw response:

for i in $(seq 1 20); do
  curl -s -o /dev/null -w '%{time_total} %{time_starttransfer}\n' \
    -H 'Cookie: <your session cookie>' \
    https://example.com/some-page/
done

The first column is total time, the second is time to first byte. If time to first byte is already high, the delay is in PHP or the database, not in assets. That distinction matters because it tells you whether to profile server side code or look at the front end. If time to first byte is low but total is high, the plugin is probably enqueuing scripts or making the browser wait, which is a different investigation.

How to find the slow WordPress plugin with a profiler

Install a query and hook profiler as a must use plugin rather than a normal one, so it loads before everything else and can attribute time accurately. A must use plugin lives in wp-content/mu-plugins/ and is loaded unconditionally. Most profilers ship with a normal plugin wrapper that simply includes the real file, and you can copy that include line into an mu-plugin file yourself. Once it is active, load the page with a query flag the profiler recognises, and it will render a table of every hook, the callbacks attached to it, and the wall time each callback consumed.

Read that table with two questions in mind. Which callbacks have the largest exclusive time, meaning time spent inside that callback and not in functions it calls? And which ones run on every request even though they only matter on a specific screen? A plugin that adds a settings page should not be doing work on the front end at all, and a plugin that hooks init to check for updates on every page load is a common offender. The profiler shows you the callback name, which usually contains the plugin slug, and that is your identification.

Confirming the culprit by bisection

A profiler can mislead you if opcache is cold or if a page was cached. Bisection is slower but harder to argue with. Deactivate half your plugins, measure, then swap halves. With a dozen plugins that is a handful of measurements, and you can script it if you are comfortable with WP-CLI:

wp plugin list --status=active --field=name > active.txt
while read -r slug; do wp plugin deactivate "$slug"; done < active.txt
# reactivate one at a time, measuring after each
while read -r slug; do
  wp plugin activate "$slug"
  curl -s -o /dev/null -w "$slug %{time_starttransfer}\n" \
    https://example.com/some-page/
done < active.txt

Do not run that against a live site with cart or checkout plugins active, because deactivating a commerce plugin mid session can drop orders. Stage the test on a clone, or at minimum take a database snapshot first. Also purge any page cache and object cache between measurements, otherwise you are timing the cache, not the plugin. If the site uses a persistent object cache, flush it with wp cache flush between runs.

Distinguishing a slow plugin from a slow query it caused

Sometimes the plugin is not slow itself. It created a database table without an index, or it stores options that are loaded on every request and never autoloaded efficiently. The profiler will show a query time spike, and you can confirm by enabling the query log and looking for the slow statement. In wp-config.php:

define( 'SAVEQUERIES', true );
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );

With SAVEQUERIES on, the profiler can list every query and its duration, and you can spot a SELECT doing a full table scan on a large table. If the fix is a missing index, you may be able to add one with a migration or a small custom plugin, which is often better than removing functionality the site depends on. If the plugin offers a setting to disable a feature you do not use, turning that off can remove the query entirely, which is the cleanest outcome.

Watch for plugins that make outbound HTTP requests during a page load, such as license checks or remote feed fetches. These show up as latency that is not CPU bound and not query bound, and they are invisible in a query profiler. A slow external call will make time to first byte spike unpredictably, with some requests fast and others slow. You can spot it by checking whether the delay correlates with a specific plugin's callback in the profiler, or by blocking outbound traffic to that host and measuring again.

Deciding to fix, replace, or remove

Once you know which plugin and which hook or query is responsible, the decision follows from three facts. Does the site actually use the feature on the page that is slow? Is there a setting, an index, or a caching layer that removes the cost without losing the feature? And is the plugin maintained in a way that suggests the problem will be fixed rather than accumulate? If the feature is used, the cost is real, and there is no setting to turn it off, replacing the plugin is usually cheaper than carrying a patch you have to reapply on every update.

If you do patch it, keep the patch in a must use plugin or a small custom plugin that hooks the offending callback and removes it, rather than editing the plugin's own files, because edits are lost on update. For example, to unhook a callback that runs on every request, you can do it after the plugin loads:

add_action( 'plugins_loaded', function () {
    remove_action( 'init', 'some_plugin_expensive_bootstrap', 10 );
}, 20 );

Verify the feature still works after unhooking, because some callbacks are load bearing even when they look optional.

Next, write down the baseline time to first byte you measured before any change, apply one change at a time, and re-measure with the same command and the same page. Keep the profiler available on staging so the next time a page feels slow you can answer the question in minutes instead of an afternoon. If the slow plugin turns out to be one you cannot replace, the patch approach above is a reasonable holding pattern, but put a note in your maintenance log so the workaround does not outlive the problem.

Related articles

Subscribe to our newsletter

Get the latest hosting tips, performance insights, and industry news.