<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: MakeWPFast</title>
    <description>The latest articles on DEV Community by MakeWPFast (@make-wp-fast).</description>
    <link>https://dev.to/make-wp-fast</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3952635%2Ffe97537d-a279-4f6c-8469-17300e5862e3.png</url>
      <title>DEV Community: MakeWPFast</title>
      <link>https://dev.to/make-wp-fast</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/make-wp-fast"/>
    <language>en</language>
    <item>
      <title>WordPress Memory Limit: How to Find What’s Eating Your PHP Memory</title>
      <dc:creator>MakeWPFast</dc:creator>
      <pubDate>Tue, 23 Jun 2026 08:15:13 +0000</pubDate>
      <link>https://dev.to/make-wp-fast/wordpress-memory-limit-how-to-find-whats-eating-your-php-memory-39nl</link>
      <guid>https://dev.to/make-wp-fast/wordpress-memory-limit-how-to-find-whats-eating-your-php-memory-39nl</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://makewpfast.com/wordpress-memory-limit-find-whats-eating-php-memory/" rel="noopener noreferrer"&gt;https://makewpfast.com/wordpress-memory-limit-find-whats-eating-php-memory/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every “fix memory exhausted” article tells you the same thing – bump &lt;code&gt;WP_MEMORY_LIMIT&lt;/code&gt; to 256M and move on. That’s not a fix. That’s duct tape. The real question nobody answers is: what’s eating your memory in the first place?&lt;/p&gt;

&lt;p&gt;I’ve profiled hundreds of WordPress sites. The pattern is always the same – someone raises the limit, things work for a while, then the error comes back. Because the actual problem is still there, just hiding behind a bigger number.&lt;/p&gt;

&lt;p&gt;Let’s And when memory exhaustion tips a site from slow into a full white-screen crash — wp-admin included, so you can’t even log in to raise the limit — that’s no longer a tuning job. At that point the fastest route back online is to &lt;a href="https://fix-wp.com/" rel="noopener noreferrer"&gt;get a crashed WordPress site fixed&lt;/a&gt;, then come back and deal with the root cause once you’re live again.&lt;/p&gt;

&lt;p&gt;actually diagnose it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding WordPress Memory – The Two Limits You Need to Know
&lt;/h2&gt;

&lt;p&gt;WordPress has two separate memory ceilings, and most people confuse them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHP’s &lt;code&gt;memory_limit&lt;/code&gt;&lt;/strong&gt; is the hard cap set in your server’s &lt;code&gt;php.ini&lt;/code&gt; (or pool config). This is the absolute maximum any PHP process can use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WordPress’s &lt;code&gt;WP_MEMORY_LIMIT&lt;/code&gt;&lt;/strong&gt; is a soft limit WordPress sets for itself. It’s defined in &lt;code&gt;wp-config.php&lt;/code&gt; and can never exceed the PHP limit.&lt;/p&gt;

&lt;p&gt;There’s also &lt;code&gt;WP_MAX_MEMORY_LIMIT&lt;/code&gt; – which applies only to admin-side operations (wp-admin). WordPress sets this to 256M by default.&lt;/p&gt;

&lt;p&gt;Here’s the thing most guides skip: if your PHP &lt;code&gt;memory_limit&lt;/code&gt; is 128M and you set &lt;code&gt;WP_MEMORY_LIMIT&lt;/code&gt; to 512M, you still only get 128M. WordPress can’t override the server. It can only request up to what PHP allows.&lt;/p&gt;

&lt;p&gt;To check your actual limits, drop this in a mu-plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// File: wp-content/mu-plugins/memory-check.php&lt;/span&gt;
&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin_notices'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;current_user_can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'manage_options'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$php_limit&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ini_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'memory_limit'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$wp_limit&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WP_MEMORY_LIMIT'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="no"&gt;WP_MEMORY_LIMIT&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'40M'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$wp_max_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WP_MAX_MEMORY_LIMIT'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="no"&gt;WP_MAX_MEMORY_LIMIT&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'256M'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$current_use&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;memory_get_usage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$peak_use&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;memory_get_peak_usage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;div class="notice notice-info"&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Memory:&amp;lt;/strong&amp;gt; PHP limit: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$php_limit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"WP limit: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$wp_limit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | WP admin limit: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$wp_max_limit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Current: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$current_use&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;MB | Peak: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$peak_use&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;MB&amp;lt;/p&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/div&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you the full picture in one glance. If your peak usage is consistently close to the limit – say above 80% – you have a problem that needs investigating, not just a bigger number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Memory Actually Goes in WordPress
&lt;/h2&gt;

&lt;p&gt;PHP memory in WordPress gets consumed by four main things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Plugin code loaded into memory.&lt;/strong&gt; Every active plugin loads its PHP files on every single request. A plugin with 50 PHP files and a bunch of class autoloading? All of that sits in memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Database query results.&lt;/strong&gt; When a plugin runs &lt;code&gt;get_posts()&lt;/code&gt; without a limit, or loads the entire options table, all that data lives in PHP memory until the request ends. I wrote about this in the context of &lt;a href="https://makewpfast.com/wordpress-autoload-the-hidden-performance-killer/" rel="noopener noreferrer"&gt;autoloaded options&lt;/a&gt; – a bloated autoload table eats memory on every single page load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Image processing.&lt;/strong&gt; When WordPress generates thumbnails or processes an uploaded image, it loads the full image into memory. A 5MB JPEG can easily consume 30-40MB of PHP memory when being manipulated by GD or Imagick.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Object caching and globals.&lt;/strong&gt; WordPress stores a lot of intermediate data in memory – post objects, user objects, cached queries. If you’re running WooCommerce with thousands of products and a poorly written product query, that’s all sitting in RAM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Measure Current Memory Usage Per Page
&lt;/h2&gt;

&lt;p&gt;Before you start disabling things randomly, measure. Add this to your theme’s &lt;code&gt;functions.php&lt;/code&gt; or better yet, as a mu-plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// File: wp-content/mu-plugins/memory-profiler.php&lt;/span&gt;
&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'shutdown'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;current_user_can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'manage_options'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DOING_AJAX'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="no"&gt;DOING_AJAX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REST_REQUEST'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="no"&gt;REST_REQUEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$peak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;memory_get_peak_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$peak_mb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$peak&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Log to debug.log&lt;/span&gt;
    &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'[Memory] %s - Peak: %sMB - URI: %s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'H:i:s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;$peak_mb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'REQUEST_URI'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'unknown'&lt;/span&gt;
    &lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure &lt;code&gt;WP_DEBUG&lt;/code&gt; and &lt;code&gt;WP_DEBUG_LOG&lt;/code&gt; are enabled in &lt;code&gt;wp-config.php&lt;/code&gt;. Then browse your site – hit the homepage, a few posts, the admin dashboard, the plugins page. Check &lt;code&gt;wp-content/debug.log&lt;/code&gt; afterwards.&lt;/p&gt;

&lt;p&gt;What you’re looking for: which pages use the most memory? If your homepage uses 45MB but the admin plugins page uses 120MB, that tells you something. If a specific post type archive spikes to 90MB, that’s worth investigating.&lt;/p&gt;

&lt;p&gt;For a deeper look at your &lt;code&gt;wp-config.php&lt;/code&gt; debug constants and what each one does, check the &lt;a href="https://makewpfast.com/the-complete-wp-config-php-performance-tuning-guide/" rel="noopener noreferrer"&gt;wp-config.php performance tuning guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Find the Memory-Hungry Plugins
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. Most guides tell you to deactivate all plugins and reactivate one by one. That works, but it’s painfully slow and doesn’t give you numbers.&lt;/p&gt;

&lt;p&gt;Here’s a mu-plugin that measures memory before and after each plugin loads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// File: wp-content/mu-plugins/plugin-memory-audit.php&lt;/span&gt;
&lt;span class="c1"&gt;// WARNING: Dev/staging only. Remove after diagnosis.&lt;/span&gt;

&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pre_current_active_plugins'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;current_user_can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'manage_options'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$active_plugins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'active_plugins'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
    &lt;span class="nv"&gt;$measurements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$active_plugins&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$plugin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;WP_PLUGIN_DIR&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;memory_get_usage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Note: plugins are already loaded at this point&lt;/span&gt;
        &lt;span class="c1"&gt;// This reads the file size as a proxy for code weight&lt;/span&gt;
        &lt;span class="nv"&gt;$size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;filesize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$measurements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$plugin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'main_file_kb'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$size&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Sort by file size descending&lt;/span&gt;
    &lt;span class="nb"&gt;uasort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$measurements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'main_file_kb'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'main_file_kb'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;div class="notice notice-info"&amp;gt;&amp;lt;pre&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Plugin Main File Sizes (larger = potentially more memory):nn"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$measurements&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$plugin&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"%-50s %6.1f KBn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$plugin&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'main_file_kb'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the real way to measure per-plugin memory impact is with the &lt;strong&gt;binary deactivation method&lt;/strong&gt;. It’s faster than one-by-one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Note your baseline peak memory (from Step 1)&lt;/li&gt;
&lt;li&gt;Deactivate half your plugins&lt;/li&gt;
&lt;li&gt;Measure again. Did memory drop significantly?&lt;/li&gt;
&lt;li&gt;If yes, the culprit is in the deactivated half. Reactivate them, deactivate the other half.&lt;/li&gt;
&lt;li&gt;Keep halving until you find the offender&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With 20 plugins, this takes 4-5 rounds instead of 20 individual tests.&lt;/p&gt;

&lt;p&gt;If you want to skip the manual work, &lt;a href="https://wordpress.org/plugins/query-monitor/" rel="noopener noreferrer"&gt;Query Monitor&lt;/a&gt; shows memory usage per component. Install it, load a page, and check the “Environment” panel for current and peak memory. The “Queries by Component” panel shows you which plugins are running the most database queries – which correlates heavily with memory use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Investigate the Usual Suspects
&lt;/h2&gt;

&lt;p&gt;In my experience diagnosing WordPress performance issues, these are the categories that eat the most memory:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Page builders (40-80MB).&lt;/strong&gt; Elementor, Divi, WPBakery – they load massive amounts of PHP code and rendering logic on every request, even on pages that don’t use the builder. If you’re using Elementor on 3 pages but it’s loading its full stack on all 200 pages, that’s a problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WooCommerce (30-60MB).&lt;/strong&gt; This is basically an application within an application. It’s legitimate memory usage, but it adds up fast when combined with WooCommerce extensions. If you have WooCommerce + 15 WooCommerce add-ons, don’t be surprised when you need 256MB+.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multilingual plugins (20-40MB).&lt;/strong&gt; WPML in particular loads a lot of translation data into memory. If you’re running a multilingual WooCommerce store, you’re stacking two heavy plugins together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SEO plugins with real-time analysis (15-30MB).&lt;/strong&gt; Yoast and Rank Math load content analysis libraries in the editor. This is mostly an admin-side concern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backup plugins during execution.&lt;/strong&gt; When a backup runs, it can temporarily spike memory way beyond normal levels. If your memory errors happen at scheduled times, check your backup schedule.&lt;/p&gt;

&lt;p&gt;For help diagnosing specific &lt;a href="https://makewpfast.com/wordpress-plugin-conflicts-how-to-diagnose-and-resolve-them/" rel="noopener noreferrer"&gt;plugin conflicts&lt;/a&gt;, I wrote a separate guide on that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Check Your Database for Memory Bloat
&lt;/h2&gt;

&lt;p&gt;A source of memory usage people rarely check: the database queries themselves.&lt;/p&gt;

&lt;p&gt;Enable &lt;code&gt;SAVEQUERIES&lt;/code&gt; temporarily in &lt;code&gt;wp-config.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SAVEQUERIES'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add this to see what’s happening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// Add to your memory-profiler mu-plugin&lt;/span&gt;
&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'shutdown'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;current_user_can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'manage_options'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SAVEQUERIES'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="no"&gt;SAVEQUERIES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$total_queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$slow_queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$total_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;queries&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$total_time&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$slow_queries&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'[Queries] %s - Total: %d - Slow (&amp;gt;50ms): %d - Time: %.3fs - URI: %s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'H:i:s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;$total_queries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$slow_queries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$total_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'REQUEST_URI'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'unknown'&lt;/span&gt;
    &lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; &lt;code&gt;SAVEQUERIES&lt;/code&gt; itself uses extra memory because it stores every query in an array. Only enable it during diagnosis, then turn it off. I’ve seen sites where enabling SAVEQUERIES was enough to push them over the memory limit – which tells you something about how many queries they’re running.&lt;/p&gt;

&lt;p&gt;If you’re seeing hundreds of queries per page load, that’s a memory problem disguised as a query problem. Every result set sits in PHP memory. A query returning 10,000 rows of post meta data? That’s all in RAM.&lt;/p&gt;

&lt;p&gt;For a deeper dive into slow queries, check the &lt;a href="https://makewpfast.com/wordpress-slow-queries/" rel="noopener noreferrer"&gt;slow queries diagnostic guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: The Autoload Trap
&lt;/h2&gt;

&lt;p&gt;This one deserves its own callout because it’s so common. Every page load in WordPress runs one query to load all autoloaded options from &lt;code&gt;wp_options&lt;/code&gt;. All of them. Into memory.&lt;/p&gt;

&lt;p&gt;Check how much data that is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="nf"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;option_value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;autoload_size&lt;/span&gt;
&lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wp_options&lt;/span&gt;
&lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;autoload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'yes'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that number is over 1MB, you have a problem. I’ve seen sites with 5-10MB of autoloaded data – that’s 5-10MB of memory consumed before WordPress even starts rendering a page.&lt;/p&gt;

&lt;p&gt;Common culprits: abandoned plugin settings that left &lt;code&gt;autoload = 'yes'&lt;/code&gt; entries behind, transients stored with autoload on, and serialized blobs from plugins that cache data in options instead of using the object cache.&lt;/p&gt;

&lt;p&gt;I wrote an entire post about &lt;a href="https://makewpfast.com/wordpress-autoload-the-hidden-performance-killer/" rel="noopener noreferrer"&gt;the autoload problem and how to fix it&lt;/a&gt;. If your autoload size is over 1MB, start there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Image Processing Spikes
&lt;/h2&gt;

&lt;p&gt;If your memory errors happen specifically during uploads, the problem is almost certainly image processing.&lt;/p&gt;

&lt;p&gt;When you upload a 4000x3000px image, WordPress generates multiple thumbnail sizes. The GD library (default) needs to hold the entire uncompressed image in memory. The formula is roughly:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;width x height x channels x bytes_per_channel&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For a 4000×3000 RGBA image: 4000 * 3000 * 4 * 1 = ~46MB. Just for one image in memory. Now multiply that by each thumbnail being generated.&lt;/p&gt;

&lt;p&gt;Solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switch to Imagick if available – it streams from disk instead of loading everything into memory&lt;/li&gt;
&lt;li&gt;Limit maximum upload dimensions with &lt;code&gt;big_image_size_threshold&lt;/code&gt; filter (WordPress 5.3+)&lt;/li&gt;
&lt;li&gt;Reduce registered image sizes – every size means another memory allocation during upload
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Limit uploaded images to 2560px max (WordPress default since 5.3)&lt;/span&gt;
&lt;span class="c1"&gt;// Lower it if you're hitting memory limits during uploads&lt;/span&gt;
&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'big_image_size_threshold'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Max dimension in pixels&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When to Actually Increase the Limit
&lt;/h2&gt;

&lt;p&gt;Sometimes you genuinely need more memory. Here’s when increasing makes sense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You’re running WooCommerce with extensions – 256M is reasonable&lt;/li&gt;
&lt;li&gt;You have a legitimate need for heavy plugins (LMS, membership, multilingual + ecommerce)&lt;/li&gt;
&lt;li&gt;You’ve already optimized and your peak usage is still near the limit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here’s how to set it properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In wp-config.php - ABOVE the "That's all, stop editing!" line&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WP_MEMORY_LIMIT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'256M'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// Frontend&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WP_MAX_MEMORY_LIMIT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'512M'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// Admin/backend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But also check your PHP config. You can’t just set the WordPress constant – the PHP limit needs to match or exceed it. Ask your host, or check via &lt;code&gt;phpinfo()&lt;/code&gt; or Site Health &amp;gt; Info &amp;gt; Server.&lt;/p&gt;

&lt;p&gt;If your &lt;a href="https://makewpfast.com/why-your-wordpress-admin-dashboard-is-so-slow/" rel="noopener noreferrer"&gt;admin dashboard is slow&lt;/a&gt;, high memory usage is often part of the puzzle. Memory pressure causes PHP to spend more time on memory management, which slows everything down.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Diagnostic Workflow
&lt;/h2&gt;

&lt;p&gt;Here’s the process I follow when someone reports memory errors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check the limits.&lt;/strong&gt; What’s PHP’s &lt;code&gt;memory_limit&lt;/code&gt;? What’s &lt;code&gt;WP_MEMORY_LIMIT&lt;/code&gt;? Are they aligned?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure baseline.&lt;/strong&gt; Drop in the memory profiler mu-plugin. Browse the site. Note peak usage on key pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check autoload size.&lt;/strong&gt; Run the SQL query. If it’s over 1MB, clean it up first – that’s often the quickest win.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binary plugin test.&lt;/strong&gt; Deactivate half, measure, narrow down. Find the top 2-3 memory consumers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide.&lt;/strong&gt; Can you replace the heavy plugin? Optimize its settings? Or do you genuinely need to increase the limit?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor.&lt;/strong&gt; Leave the memory logging mu-plugin active for a week. Watch for spikes that correlate with cron jobs, backups, or specific user actions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole process takes about an hour for most sites. And it gives you actual data instead of blindly throwing memory at the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools That Help
&lt;/h2&gt;

&lt;p&gt;A few tools I use regularly for memory diagnosis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://wordpress.org/plugins/query-monitor/" rel="noopener noreferrer"&gt;Query Monitor&lt;/a&gt;&lt;/strong&gt; – Shows memory usage, query count per component, and hooks. Essential for any WordPress developer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://wpmultitool.com" rel="noopener noreferrer"&gt;WP Multitool&lt;/a&gt;&lt;/strong&gt; – The System Info module shows your memory limits at a glance, Config Manager lets you toggle debug constants without editing files, and the Autoloader Optimizer helps clean up bloated autoload data. Full disclosure – I built this one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://wordpress.org/plugins/code-profiler/" rel="noopener noreferrer"&gt;Code Profiler&lt;/a&gt;&lt;/strong&gt; – If you need deep PHP-level profiling, this generates detailed charts showing which scripts, classes, and functions use the most resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PHP’s built-in functions&lt;/strong&gt; – &lt;code&gt;memory_get_usage()&lt;/code&gt; and &lt;code&gt;memory_get_peak_usage()&lt;/code&gt; are your friends. They’re simple, accurate, and don’t require any plugin.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;The memory exhausted error is a symptom, not a diagnosis. Increasing the limit without understanding what’s consuming the memory is like turning up the music to ignore a rattling engine.&lt;/p&gt;

&lt;p&gt;Measure first. Find the actual consumer. Then decide whether to optimize it, replace it, or genuinely allocate more memory. In most cases I’ve seen, a combination of cleaning up autoloaded data and replacing one heavy plugin solves the problem without touching the memory limit at all.&lt;/p&gt;

&lt;p&gt;If you’re seeing memory issues alongside &lt;a href="https://makewpfast.com/wordpress-database-bloat-cleaning-revisions-transients-and-spam/" rel="noopener noreferrer"&gt;database bloat&lt;/a&gt; or &lt;a href="https://makewpfast.com/finding-what-makes-wordpress-slow/" rel="noopener noreferrer"&gt;general slowness&lt;/a&gt;, those problems feed into each other. Fix them together.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>php</category>
      <category>tutorial</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Best WooCommerce Configurator Plugins 2026 (Performance Tested)</title>
      <dc:creator>MakeWPFast</dc:creator>
      <pubDate>Mon, 22 Jun 2026 08:15:12 +0000</pubDate>
      <link>https://dev.to/make-wp-fast/best-woocommerce-configurator-plugins-2026-performance-tested-40eh</link>
      <guid>https://dev.to/make-wp-fast/best-woocommerce-configurator-plugins-2026-performance-tested-40eh</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://makewpfast.com/best-woocommerce-configurator-plugins/" rel="noopener noreferrer"&gt;https://makewpfast.com/best-woocommerce-configurator-plugins/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every WooCommerce product configurator plugin promises “lightweight” and “optimized” on its sales page. So I installed 16 of them on a clean WooCommerce 10.6 site and measured what they actually do to your product pages.&lt;/p&gt;

&lt;p&gt;The results? Some plugins add literally zero extra kilobytes. Others dump 860KB of JavaScript on every product page – including the WordPress media library. On a product page. Let that sink in.&lt;/p&gt;

&lt;p&gt;Table of Contents&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How I tested&lt;/li&gt;
&lt;li&gt;The results (all 16 plugins)&lt;/li&gt;
&lt;li&gt;The good: zero-overhead plugins&lt;/li&gt;
&lt;li&gt;The acceptable: under 50KB&lt;/li&gt;
&lt;li&gt;The mediocre: 50-100KB&lt;/li&gt;
&lt;li&gt;The bad: over 300KB&lt;/li&gt;
&lt;li&gt;The ugly: YayExtra&lt;/li&gt;
&lt;li&gt;Deep dive: top 6 head-to-head&lt;/li&gt;
&lt;li&gt;What to actually pick&lt;/li&gt;
&lt;li&gt;The pattern that matters&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How I tested
&lt;/h2&gt;

&lt;p&gt;Clean WordPress 6.8.3 + &lt;a href="https://makewpfast.com/plugins/woocommerce/" rel="noopener noreferrer"&gt;WooCommerce&lt;/a&gt; 10.6.1 + GeneratePress theme. One simple product. Each plugin installed, activated, and benchmarked on the product page – then removed before testing the next one.&lt;/p&gt;

&lt;p&gt;I measured total page weight (HTML + JS + CSS), number of JavaScript and CSS files, and TTFB. All tests ran on the same Docker container with no caching. Three TTFB runs per plugin, averaged.&lt;/p&gt;

&lt;p&gt;The baseline WooCommerce product page weighs &lt;strong&gt;535KB&lt;/strong&gt; across 15 JS files and 9 CSS files. Everything below is the overhead each plugin adds on top of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;Page Weight&lt;/th&gt;
&lt;th&gt;Extra Weight&lt;/th&gt;
&lt;th&gt;JS Files&lt;/th&gt;
&lt;th&gt;CSS Files&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;STAGGS&lt;/td&gt;
&lt;td&gt;535KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WCB Configurator Builder&lt;/td&gt;
&lt;td&gt;535KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expivi 3D Configurator&lt;/td&gt;
&lt;td&gt;535KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPOM Product Addons&lt;/td&gt;
&lt;td&gt;535KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom Product Configurator&lt;/td&gt;
&lt;td&gt;535KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product Configurator (mklacroix)&lt;/td&gt;
&lt;td&gt;539KB&lt;/td&gt;
&lt;td&gt;+4KB&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advanced Product Fields&lt;/td&gt;
&lt;td&gt;543KB&lt;/td&gt;
&lt;td&gt;+8KB&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simple Product Options&lt;/td&gt;
&lt;td&gt;563KB&lt;/td&gt;
&lt;td&gt;+28KB&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual Product Configurator&lt;/td&gt;
&lt;td&gt;565KB&lt;/td&gt;
&lt;td&gt;+30KB&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flexible Product Fields&lt;/td&gt;
&lt;td&gt;583KB&lt;/td&gt;
&lt;td&gt;+48KB&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WPC Composite Products&lt;/td&gt;
&lt;td&gt;599KB&lt;/td&gt;
&lt;td&gt;+64KB&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zakeke Interactive Designer&lt;/td&gt;
&lt;td&gt;625KB&lt;/td&gt;
&lt;td&gt;+90KB&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ThemeHigh Extra Product Options&lt;/td&gt;
&lt;td&gt;627KB&lt;/td&gt;
&lt;td&gt;+92KB&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra Product Options (ThemeComplete)&lt;/td&gt;
&lt;td&gt;858KB&lt;/td&gt;
&lt;td&gt;+323KB&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YITH Product Add-Ons&lt;/td&gt;
&lt;td&gt;878KB&lt;/td&gt;
&lt;td&gt;+343KB&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YayExtra&lt;/td&gt;
&lt;td&gt;1,395KB&lt;/td&gt;
&lt;td&gt;+860KB&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Benchmarked March 2026 on WooCommerce 10.6.1 + WordPress 6.8.3. Page weight = HTML + JS + CSS, uncompressed.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The good: zero-overhead plugins
&lt;/h2&gt;

&lt;p&gt;Five plugins added literally nothing to product pages that don’t use them. This is how it should work – only load your assets when the product actually has a configurator attached.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://wordpress.org/plugins/staggs/" rel="noopener noreferrer"&gt;STAGGS&lt;/a&gt;&lt;/strong&gt; is the standout here. It’s the most feature-complete of the visual configurators (layer-based PNG stacking, 3D GLB models, conditional logic, formula pricing) and it’s free on WordPress.org with a $59/year pro tier. Zero frontend overhead unless you’ve actually configured a product. 16MB install size is a bit chunky, but that’s disk space, not page weight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://wordpress.org/plugins/wcb-configurator-builder/" rel="noopener noreferrer"&gt;WCB Configurator Builder&lt;/a&gt;&lt;/strong&gt; does the same thing right – Gutenberg-based, supports both PNG layers and GLB 3D models, zero overhead on non-configured products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expivi&lt;/strong&gt; and &lt;strong&gt;PPOM&lt;/strong&gt; also get it right, though Expivi requires a separate SaaS subscription and PPOM had some SQL errors on activation (not great).&lt;/p&gt;

&lt;h2&gt;
  
  
  The acceptable: under 50KB overhead
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://wordpress.org/plugins/product-configurator-for-woocommerce/" rel="noopener noreferrer"&gt;Product Configurator for WooCommerce&lt;/a&gt;&lt;/strong&gt; by mklacroix adds just 4KB – one tiny JS file and one small CSS file. Been around since 2015, solid reputation. This is the OG WooCommerce visual configurator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://wordpress.org/plugins/advanced-product-fields-for-woocommerce/" rel="noopener noreferrer"&gt;Advanced Product Fields&lt;/a&gt;&lt;/strong&gt; by Studio Wombat adds 8KB. Smallest install size of all tested plugins at 423KB. If you need simple product fields (text, select, checkbox, color picker) without the visual configurator stuff, this is your best bet for performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plugin Republic’s Simple Product Options&lt;/strong&gt; adds 28KB. Clean, lightweight. Only loads jQuery UI core as a dependency – nothing crazy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mediocre: 50-100KB overhead
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://wordpress.org/plugins/flexible-product-fields/" rel="noopener noreferrer"&gt;Flexible Product Fields&lt;/a&gt;&lt;/strong&gt; by WPDesk adds 48KB. The unminified front-end JS (34KB) tells me they’re not optimizing for production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://wordpress.org/plugins/wpc-composite-products/" rel="noopener noreferrer"&gt;WPC Composite Products&lt;/a&gt;&lt;/strong&gt; by WPClever adds 64KB. Loads ddslick (a dropdown library) and imagesloaded on every product page even when the product isn’t a composite. That’s wasteful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zakeke&lt;/strong&gt; adds 90KB, mainly from Glide.js (82KB) – a carousel library. Loads on every product page. Zakeke is a SaaS platform though, so the real performance cost is in the iframe/API calls that happen when you actually configure a product, which this test doesn’t capture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ThemeHigh Extra Product Options&lt;/strong&gt; adds 92KB. The big culprit is an unminified 72KB front-end.js. Just minifying it would cut that significantly. Not a great look.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bad: over 300KB overhead
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://wordpress.org/plugins/woo-extra-product-options/" rel="noopener noreferrer"&gt;Extra Product Options by ThemeComplete&lt;/a&gt;&lt;/strong&gt; adds 323KB. It loads jquery.inputmask (145KB!), jQuery UI datepicker, timepicker, and the full dashicons font – all on every product page, even when no fields are configured. That inputmask file alone is bigger than most entire plugins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://wordpress.org/plugins/yith-woocommerce-product-add-ons/" rel="noopener noreferrer"&gt;YITH Product Add-Ons&lt;/a&gt;&lt;/strong&gt; adds 343KB and 9 extra JavaScript files. Loads SelectWoo (78KB), underscore.js, jQuery UI datepicker, progressbar, wp-util – the works. The TTFB increase was also the highest at 68ms (vs 40ms baseline), suggesting significant PHP overhead too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ugly: YayExtra
&lt;/h2&gt;

&lt;p&gt;YayExtra deserves its own section. It adds &lt;strong&gt;860KB&lt;/strong&gt; to every product page. That’s not a typo.&lt;/p&gt;

&lt;p&gt;It loads the entire WordPress media library stack: media-views.min.js (111KB), mediaelement-and-player.min.js (158KB), moxie.min.js (87KB), plupload.min.js, backbone.js, wp-backbone, media-models, media-editor… 44 JavaScript files total. Plus media-views.css, wp-mediaelement.css, dashicons.&lt;/p&gt;

&lt;p&gt;This is the WordPress media uploader. On every product page. For your customers. Even when no product options are configured.&lt;/p&gt;

&lt;p&gt;Your product page goes from 535KB to 1,395KB. Your visitors are downloading the WordPress admin media library just to view a product. I genuinely don’t understand how this got past code review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep dive: comparing the top 6 head-to-head
&lt;/h2&gt;

&lt;p&gt;The initial test measured “idle” overhead – what happens when the plugin is active but no configurator is attached to the product. That’s useful, but the real question is: how do these plugins behave across your entire site?&lt;/p&gt;

&lt;p&gt;I reinstalled the six best-performing plugins and tested them across four page types: product page, shop archive, homepage, and cart. I also dug into their code to compare architecture, dependencies, and build quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Asset leakage test: do they pollute your entire site?
&lt;/h3&gt;

&lt;p&gt;This is the test most plugin reviews skip. If a configurator plugin loads JS/CSS on your homepage, your blog, your shop archive – that’s wasted bandwidth on every single page view, not just product pages.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;Product Page&lt;/th&gt;
&lt;th&gt;Shop Archive&lt;/th&gt;
&lt;th&gt;Homepage&lt;/th&gt;
&lt;th&gt;Cart&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;STAGGS&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;Zero leakage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WCB Configurator&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;Zero leakage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom Product Configurator&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;+0KB&lt;/td&gt;
&lt;td&gt;Zero leakage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product Configurator (mklacroix)&lt;/td&gt;
&lt;td&gt;+4KB&lt;/td&gt;
&lt;td&gt;+4KB&lt;/td&gt;
&lt;td&gt;+4KB&lt;/td&gt;
&lt;td&gt;+4KB&lt;/td&gt;
&lt;td&gt;Minimal leak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advanced Product Fields&lt;/td&gt;
&lt;td&gt;+8KB&lt;/td&gt;
&lt;td&gt;+8KB&lt;/td&gt;
&lt;td&gt;+8KB&lt;/td&gt;
&lt;td&gt;+8KB&lt;/td&gt;
&lt;td&gt;Every page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual Product Configurator&lt;/td&gt;
&lt;td&gt;+31KB&lt;/td&gt;
&lt;td&gt;+31KB&lt;/td&gt;
&lt;td&gt;+31KB&lt;/td&gt;
&lt;td&gt;+24KB&lt;/td&gt;
&lt;td&gt;Every page&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Extra page weight added beyond baseline, per page type. “Zero leakage” = wp_register + conditional wp_enqueue only when needed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three plugins pass with flying colors: &lt;strong&gt;STAGGS, WCB Configurator Builder, and Custom Product Configurator&lt;/strong&gt; add exactly zero bytes to any page that doesn’t use a configurator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product Configurator by mklacroix&lt;/strong&gt; has a minor leak – it loads a 624-byte general.js and 2.9KB CSS on every page. Small, but unnecessary. Looking at the code, &lt;code&gt;load_scripts()&lt;/code&gt; enqueues a base CSS+JS pair before the conditional check runs. Would be trivial to fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advanced Product Fields&lt;/strong&gt; loads its frontend.min.js (6.3KB) and frontend.min.css (1.3KB) on every single page via unconditional &lt;code&gt;wp_enqueue&lt;/code&gt;. No &lt;code&gt;is_product()&lt;/code&gt; check anywhere. For a product-addons plugin, this is a clear oversight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visual Product Configurator&lt;/strong&gt; is the worst offender here – fabric.js, accounting.js, oriontip, and its own vpc-public.js load on every page. That’s 31KB of JavaScript your homepage visitors download for absolutely no reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code architecture comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;PHP Files&lt;/th&gt;
&lt;th&gt;JS Shipped&lt;/th&gt;
&lt;th&gt;Disk Size&lt;/th&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;jQuery-Free?&lt;/th&gt;
&lt;th&gt;Build Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Custom Product Configurator&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1.1MB&lt;/td&gt;
&lt;td&gt;React&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Webpack/Babel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advanced Product Fields&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;423KB&lt;/td&gt;
&lt;td&gt;jQuery&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Manual minify&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product Configurator (mklacroix)&lt;/td&gt;
&lt;td&gt;304&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;6.1MB&lt;/td&gt;
&lt;td&gt;jQuery + Pixi.js&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;npm build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STAGGS&lt;/td&gt;
&lt;td&gt;379&lt;/td&gt;
&lt;td&gt;201&lt;/td&gt;
&lt;td&gt;15.3MB&lt;/td&gt;
&lt;td&gt;jQuery (front) / React (admin)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;npm build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual Product Configurator&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;18.7MB&lt;/td&gt;
&lt;td&gt;jQuery + Fabric.js&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;None visible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WCB Configurator Builder&lt;/td&gt;
&lt;td&gt;489&lt;/td&gt;
&lt;td&gt;202&lt;/td&gt;
&lt;td&gt;19.1MB&lt;/td&gt;
&lt;td&gt;jQuery (front) / React (admin)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;npm build&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Custom Product Configurator&lt;/strong&gt; stands out architecturally. Only 3 PHP files. React-based with a proper webpack build pipeline. Zero jQuery dependency on the frontend – it uses native &lt;code&gt;fetch()&lt;/code&gt; for API calls. The leanest codebase by far.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advanced Product Fields&lt;/strong&gt; is the smallest install at 423KB and ships only 2 JS + 2 CSS files, all minified. It’s a simple jQuery plugin that does one thing well. If you just need custom fields and nothing visual, this is the most efficient choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product Configurator by mklacroix&lt;/strong&gt; has the most mature codebase – 304 PHP files with proper namespace structure, 32 minified JS files, and uses Pixi.js for canvas-based layer rendering. It also includes &lt;code&gt;fetch()&lt;/code&gt; alongside jQuery, suggesting a gradual modernization effort. It’s the only one (besides APF) with explicit HPOS compatibility declared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STAGGS and WCB&lt;/strong&gt; have similar architectures: jQuery on the frontend for the configurator UI, React for the admin panel, Carbon Fields for custom meta. Both ship 200+ JS files but that’s mostly admin/build assets – they’re well-disciplined about what reaches the frontend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visual Product Configurator&lt;/strong&gt; bundles 18.7MB of files for 20 PHP files and no visible build tool. It ships Fabric.js (a canvas library) and FontAwesome as dependencies, loaded unconditionally. The file structure suggests no build pipeline at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency quality
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;External JS deps loaded&lt;/th&gt;
&lt;th&gt;Minified?&lt;/th&gt;
&lt;th&gt;Modern API usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Custom Product Configurator&lt;/td&gt;
&lt;td&gt;None (self-contained React build)&lt;/td&gt;
&lt;td&gt;Bundled&lt;/td&gt;
&lt;td&gt;fetch(), React 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advanced Product Fields&lt;/td&gt;
&lt;td&gt;jQuery only&lt;/td&gt;
&lt;td&gt;Yes (2/2)&lt;/td&gt;
&lt;td&gt;jQuery $.ajax&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STAGGS&lt;/td&gt;
&lt;td&gt;Swiper, jQuery UI, Lightbox&lt;/td&gt;
&lt;td&gt;Yes (7 min files)&lt;/td&gt;
&lt;td&gt;jQuery + some fetch()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WCB Configurator&lt;/td&gt;
&lt;td&gt;Fabric.js, Lightbox&lt;/td&gt;
&lt;td&gt;Partial (8 min files)&lt;/td&gt;
&lt;td&gt;jQuery + some fetch()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product Configurator (mklacroix)&lt;/td&gt;
&lt;td&gt;Pixi.js, html2canvas, Tippy, Popper&lt;/td&gt;
&lt;td&gt;Yes (32 min files)&lt;/td&gt;
&lt;td&gt;jQuery + fetch()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual Product Configurator&lt;/td&gt;
&lt;td&gt;Fabric.js, FontAwesome, accounting.js, oriontip, serializejson&lt;/td&gt;
&lt;td&gt;Partial (15 min files)&lt;/td&gt;
&lt;td&gt;jQuery only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Feature comparison: what you actually get
&lt;/h3&gt;

&lt;p&gt;These plugins split into two categories. &lt;strong&gt;Visual configurators&lt;/strong&gt; show a live image preview as customers pick options (think customizing a shoe or a piece of furniture). &lt;strong&gt;Product option builders&lt;/strong&gt; add form fields like text inputs, dropdowns, and checkboxes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;STAGGS&lt;/th&gt;
&lt;th&gt;WCB&lt;/th&gt;
&lt;th&gt;Product Config (mkl)&lt;/th&gt;
&lt;th&gt;Custom Product Config&lt;/th&gt;
&lt;th&gt;VPC (Orion)&lt;/th&gt;
&lt;th&gt;APF (Studio Wombat)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Visual&lt;/td&gt;
&lt;td&gt;Visual&lt;/td&gt;
&lt;td&gt;Visual&lt;/td&gt;
&lt;td&gt;Visual&lt;/td&gt;
&lt;td&gt;Visual&lt;/td&gt;
&lt;td&gt;Form fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image layer stacking&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (templates)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3D / GLB models&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple views&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (Pro)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conditional logic&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;Coming soon&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamic pricing&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Formula-based pricing&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text/image overlay&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Save &amp;amp; share configuration&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF download&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inventory per option&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AR (try in room)&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works without WooCommerce&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WPML / Polylang&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HPOS compatible&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free tier available?&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro pricing&lt;/td&gt;
&lt;td&gt;~$59/yr&lt;/td&gt;
&lt;td&gt;Not listed&lt;/td&gt;
&lt;td&gt;From $49/yr&lt;/td&gt;
&lt;td&gt;Coming&lt;/td&gt;
&lt;td&gt;Not listed&lt;/td&gt;
&lt;td&gt;From $49/yr&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Pros and cons: the honest version
&lt;/h3&gt;

&lt;h4&gt;
  
  
  STAGGS – Best overall visual configurator
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Zero page weight overhead. Most complete free tier among visual configurators. Layer stacking, multiple views, real-time pricing, 10+ display templates included for free. Pro adds 3D/AR, conditional logic, formula pricing, analytics. Can work as a standalone product builder without WooCommerce. Actively maintained (last update March 2026). Won “Best Ecommerce Product Configurator 2025” from Corporate Vision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; 15.3MB disk footprint is the second largest (lots of admin React components). jQuery-dependent frontend. No HPOS compatibility declared. Most useful features locked behind Pro ($59/yr).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Furniture, jewelry, fashion, any product where customers need to see a visual preview.&lt;/p&gt;

&lt;h4&gt;
  
  
  WCB Configurator Builder – Best for Gutenberg users
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Zero overhead. Gutenberg-native builder (drag &amp;amp; drop in the block editor). Supports both PNG layers AND GLB 3D models in the free version – that’s rare. Visual drag/resize/rotate controls. Newest of the bunch (actively adding features).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Largest disk footprint at 19.1MB. Smallest user base (1,437 downloads). No HPOS. Gutenberg dependency means it won’t work with Classic Editor. Feature set is less mature than STAGGS or Product Configurator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Stores already invested in the block editor who want 3D configurators without paying for Pro.&lt;/p&gt;

&lt;h4&gt;
  
  
  Product Configurator by mklacroix – Best for developers
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Most mature codebase (since 2015). HPOS compatible. Uses Pixi.js for proper canvas rendering (smoother than DOM-based layer stacking). 32 minified JS files show disciplined build process. Hooks and filters for customization. StellarWP ecosystem (iThemes/Liquid Web backing). 4.8/5 with 42 reviews.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Leaks 4KB to all pages (minor). 6.1MB install. Heavy jQuery dependency. The pro add-on ecosystem means the “full” product gets expensive fast. Requires WooCommerce (no standalone mode).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Developers who want hooks/filters and a proven, well-supported plugin backed by a real company.&lt;/p&gt;

&lt;h4&gt;
  
  
  Custom Product Configurator (zechkonja) – Best code quality
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Cleanest architecture of all tested. Only 3 PHP files. React-based, zero jQuery dependency. Proper webpack build. Native fetch() API. Smallest footprint after APF. Ready-made templates for quick setup. Conditionally loads only on configured products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Brand new (very few installs). No conditional logic yet (“coming soon”). No multi-view, no save/share, no PDF. Had PHP warnings on activation (undefined variable in db-install.php). Limited feature set compared to STAGGS or Product Configurator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Performance-obsessed developers who need a simple configurator now and don’t mind a newer, less proven plugin.&lt;/p&gt;

&lt;h4&gt;
  
  
  Visual Product Configurator by Orion Origin – Not recommended
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Large add-on ecosystem (text, form builder, image upload, multiple views). Composite product building support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Loads 31KB of assets on every page including homepage. No build pipeline visible. Bundles FontAwesome (loading a full icon font for a configurator). Fabric.js dependency adds weight. 3.4/5 rating (lowest tested). No HPOS. 18.7MB disk for 20 PHP files suggests lots of bundled vendor assets. Last major update Oct 2025.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Skip this one. STAGGS does everything it does with zero overhead.&lt;/p&gt;

&lt;h4&gt;
  
  
  Advanced Product Fields by Studio Wombat – Best for simple fields
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Smallest install (423KB). Only 2 JS + 2 CSS files. Clean, focused codebase. HPOS compatible. Does one thing well: custom product fields. Price calculation, conditional logic (Pro), file uploads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Loads 8KB on every page (no is_product() check). No visual configurator at all – it’s just form fields. jQuery dependent. Not really a “configurator” in the visual sense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Stores that need custom text fields, dropdowns, or checkboxes on products – and nothing more.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to actually pick
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Visual product configurator&lt;/strong&gt; (layer-based image stacking, 3D models): &lt;strong&gt;STAGGS&lt;/strong&gt;. Zero overhead, most features, actively maintained, free tier available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simple product fields&lt;/strong&gt; (text, dropdowns, checkboxes, color pickers): &lt;strong&gt;Advanced Product Fields&lt;/strong&gt; by Studio Wombat. 8KB overhead, 423KB install, clean code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product option builder with conditional logic&lt;/strong&gt;: &lt;strong&gt;Product Configurator for WooCommerce&lt;/strong&gt; by mklacroix. 4KB overhead, proven track record since 2015.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composite/kit products&lt;/strong&gt;: &lt;strong&gt;WPC Composite Products&lt;/strong&gt;. 64KB overhead isn’t ideal but it’s the most complete free composite builder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3D/AR configurator&lt;/strong&gt;: &lt;strong&gt;STAGGS Pro&lt;/strong&gt; (supports GLB/GLTF + AR) or &lt;strong&gt;Expivi&lt;/strong&gt; if you need full 3D modeling capabilities and don’t mind the SaaS cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern that matters
&lt;/h2&gt;

&lt;p&gt;The #1 performance indicator isn’t the plugin’s feature list – it’s whether the plugin conditionally loads its assets. The good plugins check “does this product actually use my configurator?” before enqueueing scripts. The bad ones dump everything on every product page and hope the browser sorts it out.&lt;/p&gt;

&lt;p&gt;If you’re evaluating any configurator plugin not in this list, here’s a quick test: activate it, then check a product page that doesn’t use the configurator. If you see new JS/CSS files in the source – that’s a red flag.&lt;/p&gt;

&lt;p&gt;Your customers don’t have your patience. Every extra 100KB is measurably slower on mobile. Pick the lightest tool that does what you need. For more WordPress performance insights, check our &lt;a href="https://makewpfast.com/plugins/" rel="noopener noreferrer"&gt;plugin performance benchmarks&lt;/a&gt; and &lt;a href="https://makewpfast.com/compare/" rel="noopener noreferrer"&gt;plugin comparisons&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the best WooCommerce configurator plugin for performance?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/staggs/" rel="noopener noreferrer"&gt;STAGGS&lt;/a&gt; is the best overall. It adds zero extra page weight to products that don’t use a configurator, offers the most features in its free tier (layer stacking, multiple views, real-time pricing), and only loads assets on configured product pages. In our benchmark of 16 plugins, it tied for the lightest with WCB Configurator Builder, Custom Product Configurator, Expivi, and PPOM – all at exactly 0KB overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do WooCommerce product configurator plugins slow down my site?
&lt;/h3&gt;

&lt;p&gt;It depends entirely on the plugin. In our tests, 5 out of 16 plugins added zero overhead when activated. However, others were much worse – YayExtra added 860KB to every product page (loading the entire WordPress media library), and YITH Product Add-Ons added 343KB. The key differentiator is whether the plugin conditionally loads assets only on pages that actually use the configurator.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is asset leakage in WordPress plugins?
&lt;/h3&gt;

&lt;p&gt;Asset leakage is when a plugin loads its JavaScript and CSS files on pages where they’re not needed – like your homepage, blog, or shop archive. In our deep benchmark, we found that Advanced Product Fields loads 8KB on every page and Visual Product Configurator loads 31KB on every page, even though their functionality is only needed on individual product pages. Well-coded plugins like STAGGS and WCB Configurator use &lt;code&gt;wp_register&lt;/code&gt; and conditional checks to only load assets where needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use a free or premium WooCommerce configurator?
&lt;/h3&gt;

&lt;p&gt;Start with the free tier. STAGGS, WCB Configurator Builder, Product Configurator by mklacroix, and Advanced Product Fields all have usable free versions. If you need visual layer stacking and dynamic pricing, STAGGS free covers that. Upgrade to Pro only when you need conditional logic, 3D/AR, formula pricing, or PDF downloads. Most stores don’t need those from day one.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the lightest WooCommerce product options plugin?
&lt;/h3&gt;

&lt;p&gt;For simple product fields (text, dropdowns, checkboxes), &lt;a href="https://wordpress.org/plugins/advanced-product-fields-for-woocommerce/" rel="noopener noreferrer"&gt;Advanced Product Fields&lt;/a&gt; by Studio Wombat is the lightest at just 423KB installed with only 8KB of frontend assets. For visual product configurators specifically, STAGGS and WCB Configurator Builder both add 0KB of overhead to non-configured product pages.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>performance</category>
      <category>testing</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>MariaDB 10.11 vs 11.4 for WordPress: Should you update? YES! [Benchmark]</title>
      <dc:creator>MakeWPFast</dc:creator>
      <pubDate>Mon, 22 Jun 2026 08:15:11 +0000</pubDate>
      <link>https://dev.to/make-wp-fast/mariadb-1011-vs-114-for-wordpress-should-you-update-yes-benchmark-1j8a</link>
      <guid>https://dev.to/make-wp-fast/mariadb-1011-vs-114-for-wordpress-should-you-update-yes-benchmark-1j8a</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://makewpfast.com/mariadb-10-11-vs-11-4-wordpress-benchmark/" rel="noopener noreferrer"&gt;https://makewpfast.com/mariadb-10-11-vs-11-4-wordpress-benchmark/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Saw a thread on Reddit – someone on Cloudways asking if upgrading MariaDB from 10.11 to 11.4 is worth it for WooCommerce. Everyone had opinions, nobody had numbers.&lt;/p&gt;

&lt;p&gt;So I ran the actual benchmarks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;Two identical Docker containers, same everything except the database version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress 6.8.3 with PHP 8.3&lt;/li&gt;
&lt;li&gt;WooCommerce active on both&lt;/li&gt;
&lt;li&gt;~1,000 posts with meta, categories, tags&lt;/li&gt;
&lt;li&gt;200 WooCommerce products with prices and stock&lt;/li&gt;
&lt;li&gt;5,000 comments across posts&lt;/li&gt;
&lt;li&gt;100 users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One runs &lt;strong&gt;MariaDB 10.11.16&lt;/strong&gt; (current LTS), the other &lt;strong&gt;MariaDB 11.4.10&lt;/strong&gt; (new LTS).&lt;/p&gt;

&lt;p&gt;Each test ran 20 times. I’m using medians, not averages – outliers don’t mess up the results that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big One: 40x Faster Subquery Updates
&lt;/h2&gt;

&lt;p&gt;This is the most interesting finding. MariaDB 11.4 added semi-join optimization for UPDATE and DELETE statements. What that means in practice – when your UPDATE uses a subquery (and WordPress plugins do this more than you’d think), 11.4 can now optimize it the same way it optimizes SELECT queries.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query Type&lt;/th&gt;
&lt;th&gt;10.11&lt;/th&gt;
&lt;th&gt;11.4&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UPDATE with subquery&lt;/td&gt;
&lt;td&gt;5.086 ms&lt;/td&gt;
&lt;td&gt;0.128 ms&lt;/td&gt;
&lt;td&gt;97.5% faster (40x)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE with subquery&lt;/td&gt;
&lt;td&gt;0.090 ms&lt;/td&gt;
&lt;td&gt;0.070 ms&lt;/td&gt;
&lt;td&gt;22% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That UPDATE went from 5ms to 0.1ms. Think about what that means for plugins running cleanup queries, WooCommerce processing orders, or your spam cleanup cron jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  DATE() Queries: 8x Faster
&lt;/h2&gt;

&lt;p&gt;WordPress uses date-based queries everywhere. Archive pages, scheduled posts, date filtering in wp-admin.&lt;/p&gt;

&lt;p&gt;The problem with 10.11 – a query like &lt;code&gt;WHERE YEAR(post_date) = 2025&lt;/code&gt; forces a full table scan because the function call kills index usage. MariaDB 11.4 made these “sargable”, meaning the optimizer rewrites them to actually use the index.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query Type&lt;/th&gt;
&lt;th&gt;10.11&lt;/th&gt;
&lt;th&gt;11.4&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DATE function with index&lt;/td&gt;
&lt;td&gt;0.630 ms&lt;/td&gt;
&lt;td&gt;0.081 ms&lt;/td&gt;
&lt;td&gt;87% faster (8x)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every WordPress archive page benefits from this. That’s a real win.&lt;/p&gt;

&lt;h2&gt;
  
  
  All 14 SQL Benchmarks
&lt;/h2&gt;

&lt;p&gt;Here’s the full picture – 14 query patterns that simulate what WordPress actually does:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;10.11 (ms)&lt;/th&gt;
&lt;th&gt;11.4 (ms)&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Autoloaded options&lt;/td&gt;
&lt;td&gt;0.089&lt;/td&gt;
&lt;td&gt;0.077&lt;/td&gt;
&lt;td&gt;-13.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single post + meta&lt;/td&gt;
&lt;td&gt;0.163&lt;/td&gt;
&lt;td&gt;0.150&lt;/td&gt;
&lt;td&gt;-8.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Archive page (posts+meta+taxonomy)&lt;/td&gt;
&lt;td&gt;3.428&lt;/td&gt;
&lt;td&gt;3.175&lt;/td&gt;
&lt;td&gt;-7.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correlated subquery (top comments)&lt;/td&gt;
&lt;td&gt;1.417&lt;/td&gt;
&lt;td&gt;1.352&lt;/td&gt;
&lt;td&gt;-4.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LIKE search&lt;/td&gt;
&lt;td&gt;0.103&lt;/td&gt;
&lt;td&gt;0.107&lt;/td&gt;
&lt;td&gt;+3.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregate (posts per category)&lt;/td&gt;
&lt;td&gt;0.295&lt;/td&gt;
&lt;td&gt;0.232&lt;/td&gt;
&lt;td&gt;-21.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UPDATE with subquery&lt;/td&gt;
&lt;td&gt;5.086&lt;/td&gt;
&lt;td&gt;0.128&lt;/td&gt;
&lt;td&gt;-97.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE with subquery&lt;/td&gt;
&lt;td&gt;0.090&lt;/td&gt;
&lt;td&gt;0.070&lt;/td&gt;
&lt;td&gt;-22.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WooCommerce product listing&lt;/td&gt;
&lt;td&gt;0.283&lt;/td&gt;
&lt;td&gt;0.256&lt;/td&gt;
&lt;td&gt;-9.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WooCommerce product search&lt;/td&gt;
&lt;td&gt;0.392&lt;/td&gt;
&lt;td&gt;0.395&lt;/td&gt;
&lt;td&gt;+0.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WooCommerce price filter&lt;/td&gt;
&lt;td&gt;0.243&lt;/td&gt;
&lt;td&gt;0.229&lt;/td&gt;
&lt;td&gt;-5.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk meta read (500 rows)&lt;/td&gt;
&lt;td&gt;0.665&lt;/td&gt;
&lt;td&gt;0.481&lt;/td&gt;
&lt;td&gt;-27.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DATE() function index&lt;/td&gt;
&lt;td&gt;0.630&lt;/td&gt;
&lt;td&gt;0.081&lt;/td&gt;
&lt;td&gt;-87.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ORDER BY meta value&lt;/td&gt;
&lt;td&gt;1.037&lt;/td&gt;
&lt;td&gt;0.702&lt;/td&gt;
&lt;td&gt;-32.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;11.4 wins 12 out of 14 tests. The two where 10.11 was “faster” (LIKE search, WooCommerce product search) are within noise – basically identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ok But What About Actual Page Loads?
&lt;/h2&gt;

&lt;p&gt;SQL numbers are nice, but what does it feel like in the browser? I measured TTFB for real WordPress pages:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Page&lt;/th&gt;
&lt;th&gt;10.11&lt;/th&gt;
&lt;th&gt;11.4&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Homepage&lt;/td&gt;
&lt;td&gt;28.8 ms&lt;/td&gt;
&lt;td&gt;25.9 ms&lt;/td&gt;
&lt;td&gt;-10.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single post&lt;/td&gt;
&lt;td&gt;26.1 ms&lt;/td&gt;
&lt;td&gt;24.5 ms&lt;/td&gt;
&lt;td&gt;-6.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Category archive&lt;/td&gt;
&lt;td&gt;25.6 ms&lt;/td&gt;
&lt;td&gt;24.2 ms&lt;/td&gt;
&lt;td&gt;-5.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WooCommerce shop&lt;/td&gt;
&lt;td&gt;30.1 ms&lt;/td&gt;
&lt;td&gt;28.8 ms&lt;/td&gt;
&lt;td&gt;-4.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REST API (10 posts)&lt;/td&gt;
&lt;td&gt;50.9 ms&lt;/td&gt;
&lt;td&gt;51.8 ms&lt;/td&gt;
&lt;td&gt;+1.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;4-10% faster across the board. Homepage got the biggest boost at 10%.&lt;/p&gt;

&lt;p&gt;Worth noting – these are uncached numbers. If you’re running Redis or Memcached, the database differences get smoothed out. But for uncached requests – admin pages, WooCommerce cart, checkout, anything for logged-in users – that’s where 11.4 helps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under Concurrent Load
&lt;/h2&gt;

&lt;p&gt;I threw Apache Bench at both environments with different concurrency levels (200 total requests each):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concurrency&lt;/th&gt;
&lt;th&gt;10.11 (req/s)&lt;/th&gt;
&lt;th&gt;11.4 (req/s)&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;39.09&lt;/td&gt;
&lt;td&gt;39.98&lt;/td&gt;
&lt;td&gt;+2.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;162.20&lt;/td&gt;
&lt;td&gt;169.12&lt;/td&gt;
&lt;td&gt;+4.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;214.03&lt;/td&gt;
&lt;td&gt;202.14&lt;/td&gt;
&lt;td&gt;-5.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;203.08&lt;/td&gt;
&lt;td&gt;204.44&lt;/td&gt;
&lt;td&gt;+0.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Basically a wash. Slight edge to 11.4 at low concurrency, essentially tied at higher levels. The c=10 dip for 11.4 is probably Docker Desktop noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write Performance
&lt;/h2&gt;

&lt;p&gt;50 posts created via WP-CLI:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;10.11&lt;/th&gt;
&lt;th&gt;11.4&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total time&lt;/td&gt;
&lt;td&gt;38.8 s&lt;/td&gt;
&lt;td&gt;37.8 s&lt;/td&gt;
&lt;td&gt;-2.5% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per post&lt;/td&gt;
&lt;td&gt;776 ms&lt;/td&gt;
&lt;td&gt;757 ms&lt;/td&gt;
&lt;td&gt;-2.5% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;2.5% faster on writes. Nothing dramatic, but it’s not slower either.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did Anything Break?
&lt;/h2&gt;

&lt;p&gt;No. Zero issues with WordPress 6.8.3, WooCommerce, and PHP 8.3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No errors&lt;/li&gt;
&lt;li&gt;No deprecated warnings&lt;/li&gt;
&lt;li&gt;All 14 benchmark queries worked identically&lt;/li&gt;
&lt;li&gt;WooCommerce installed and activated fine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WordPress talks to the database through &lt;code&gt;$wpdb&lt;/code&gt;, and MariaDB 11.4 keeps full backward compatibility. If it works on 10.11, it’ll work on 11.4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should You Upgrade?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes, if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You run WooCommerce – the query improvements directly help product and order queries&lt;/li&gt;
&lt;li&gt;Your site has lots of posts (10k+) and uses archive or date-based queries&lt;/li&gt;
&lt;li&gt;You use plugins that run batch operations or cleanup queries with subqueries&lt;/li&gt;
&lt;li&gt;Your host offers 11.4 as an option (Cloudways, RunCloud, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Maybe hold off if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your 10.11 setup is stable and you have no performance complaints&lt;/li&gt;
&lt;li&gt;You can’t easily roll back – test in staging first&lt;/li&gt;
&lt;li&gt;You’re running high-traffic WooCommerce – test with your actual data before switching production&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;MariaDB 11.4 is worth the upgrade. The optimizer improvements are real, not just synthetic benchmark stuff:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;40x faster subquery UPDATE/DELETE&lt;/strong&gt; – the standout improvement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8x faster date queries&lt;/strong&gt; – helps every archive page&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;28-32% faster meta queries&lt;/strong&gt; – good for page builders and WooCommerce&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4-10% faster page loads&lt;/strong&gt; – measurable TTFB gains&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No compatibility issues, no regressions. If your host has 11.4 available, I don’t see a reason not to switch.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How I tested: Two identical Docker containers (WordPress 6.8.3, PHP 8.3, WooCommerce) on macOS Apple Silicon. MariaDB 10.11.16 vs 11.4.10. Each SQL test ran 20 times, median reported. TTFB measured with curl over 10 runs. Concurrent load via Apache Bench, 200 requests per level. Writes via WP-CLI, 50 posts. All on warm databases after initial warmup.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>performance</category>
      <category>testing</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>How to Find Which Plugin Is Slowing Down Your WordPress Site</title>
      <dc:creator>MakeWPFast</dc:creator>
      <pubDate>Sun, 21 Jun 2026 08:15:10 +0000</pubDate>
      <link>https://dev.to/make-wp-fast/how-to-find-which-plugin-is-slowing-down-your-wordpress-site-2p1f</link>
      <guid>https://dev.to/make-wp-fast/how-to-find-which-plugin-is-slowing-down-your-wordpress-site-2p1f</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://makewpfast.com/find-which-plugin-slowing-down-wordpress/" rel="noopener noreferrer"&gt;https://makewpfast.com/find-which-plugin-slowing-down-wordpress/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Your WordPress site is slow. You have 20-something plugins installed and you’re pretty sure one of them is the problem. But which one?&lt;/p&gt;

&lt;p&gt;This is probably the most common question I get from site owners. And honestly, the answer isn’t always straightforward – because it’s rarely just one plugin. It’s usually a combination of poorly written callbacks, excessive database queries, and plugins loading assets on every single page whether they need to or not.&lt;/p&gt;

&lt;p&gt;Let me walk you through how to actually &lt;strong&gt;find which plugin is slowing down your WordPress site&lt;/strong&gt;, from the basic manual approach to the proper diagnostic tools that show you exactly what’s happening under the hood.&lt;/p&gt;

&lt;p&gt;One gut-check before we dig in: this guide assumes your site is slow but still loading. If it isn’t just slow but fully down — white screen, a 500 error, nothing rendering at all — that’s an emergency, not a tuning problem, and &lt;a href="https://fix-wp.com/" rel="noopener noreferrer"&gt;emergency WordPress repair&lt;/a&gt; can get a crashed WordPress site back online in about an hour. Still up and just sluggish? Then let’s find the culprit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Manual Method: Disable One by One
&lt;/h2&gt;

&lt;p&gt;This is what every WordPress tutorial tells you to do. And it works – sort of.&lt;/p&gt;

&lt;p&gt;The process is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deactivate all plugins.&lt;/li&gt;
&lt;li&gt;Check your page load time (use your browser’s DevTools network tab or &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;PageSpeed Insights&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Reactivate plugins one at a time.&lt;/li&gt;
&lt;li&gt;After each activation, check the load time again.&lt;/li&gt;
&lt;li&gt;When the load time jumps, you found your culprit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Simple, right? Here’s why this method is problematic in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the Manual Method Falls Short
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;It takes forever.&lt;/strong&gt; If you have 25 plugins, you’re looking at 25+ rounds of deactivate-measure-reactivate. Each measurement should be done 3 times minimum to account for variance. That’s 75 page loads just to find one slow plugin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It misses interactions.&lt;/strong&gt; Plugin A might be fine on its own. Plugin B might be fine on its own. But together they trigger something – maybe a shared hook that fires twice, or a conflict that causes redundant database queries. Disable-and-test won’t catch this because you’re testing plugins in isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It doesn’t tell you &lt;em&gt;why&lt;/em&gt;.&lt;/strong&gt; Ok, you found that WooCommerce slows your site by 400ms. Great. Now what? WooCommerce has hundreds of hooks and callbacks. Which specific one is the bottleneck? The manual method can’t tell you that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your site is down during testing.&lt;/strong&gt; If this is a production site, deactivating plugins means breaking functionality for real visitors. Not ideal.&lt;/p&gt;

&lt;p&gt;The manual method is fine as a last resort. But there are much better approaches. Let me show you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: Query Monitor – Free and Solid
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/query-monitor/" rel="noopener noreferrer"&gt;Query Monitor&lt;/a&gt; is the go-to debugging plugin for WordPress developers. It’s free, it’s well-maintained by &lt;a href="https://github.com/johnbillion/query-monitor" rel="noopener noreferrer"&gt;John Blackbourn&lt;/a&gt;, and it gives you a detailed breakdown of what happens during every page load.&lt;/p&gt;

&lt;p&gt;Install it, activate it, and you’ll see a new toolbar at the top of your admin bar showing page generation time, memory usage, and query count.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Query Monitor Shows You
&lt;/h3&gt;

&lt;p&gt;The panels you care about for finding slow plugins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Queries by Component&lt;/strong&gt; – shows which plugin or theme is responsible for each database query, grouped and sorted. This is huge. If a plugin is running 50 queries per page load, you’ll see it immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks in Use&lt;/strong&gt; – lists every hook that fired on the current page, with priority, callback function, and the component (plugin/theme) responsible. You can filter by component to see everything a specific plugin is doing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP API Calls&lt;/strong&gt; – shows external HTTP requests. This is often where hidden slowness lives. A plugin checking for updates, hitting an external API, or loading remote data on every page load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scripts and Styles&lt;/strong&gt; – shows every CSS and JS file enqueued, by which plugin. If a contact form plugin is loading its assets on your homepage, you’ll see it here.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Limitation of Query Monitor
&lt;/h3&gt;

&lt;p&gt;Query Monitor is excellent at showing you &lt;em&gt;what&lt;/em&gt; happened during a page load. It shows queries, hooks, assets, and HTTP calls grouped by plugin.&lt;/p&gt;

&lt;p&gt;But it doesn’t directly time individual hooks and callbacks. You can see that a plugin registered 15 callbacks across various hooks – but you can’t see that one specific callback on &lt;code&gt;wp_head&lt;/code&gt; at priority 10 took 200ms while the others took 1ms each.&lt;/p&gt;

&lt;p&gt;Query Monitor does have a &lt;a href="https://querymonitor.com/wordpress-debugging/profiling-and-logging/" rel="noopener noreferrer"&gt;profiling API&lt;/a&gt; where you can manually wrap code sections with &lt;code&gt;do_action( 'qm/start', 'my-timer' )&lt;/code&gt; and &lt;code&gt;do_action( 'qm/stop', 'my-timer' )&lt;/code&gt;. But that requires editing code – you need to know where to look first.&lt;/p&gt;

&lt;p&gt;For database-heavy plugins, Query Monitor is perfect. For hook-level timing, you need something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 3: Hook Profiling – Finding the Exact Callback
&lt;/h2&gt;

&lt;p&gt;This is where things get precise. Instead of disabling entire plugins, a hook profiler measures the execution time of every single callback that fires during a page load.&lt;/p&gt;

&lt;p&gt;Think of it this way: WordPress processes a request by firing a sequence of hooks – &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;wp&lt;/code&gt;, &lt;code&gt;template_redirect&lt;/code&gt;, &lt;code&gt;wp_head&lt;/code&gt;, &lt;code&gt;the_content&lt;/code&gt;, &lt;code&gt;wp_footer&lt;/code&gt;, etc. Each hook can have dozens of callbacks attached by your theme and plugins. A hook profiler wraps each callback with a timer and reports the results.&lt;/p&gt;

&lt;p&gt;This means you don’t just find the slow &lt;em&gt;plugin&lt;/em&gt; – you find the slow &lt;em&gt;function&lt;/em&gt;. That’s a completely different level of diagnostic information.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a Hook Profiler Reveals
&lt;/h3&gt;

&lt;p&gt;A proper hook profiler will show you something like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Callback &lt;code&gt;SomePlugin::generate_critical_css()&lt;/code&gt; on hook &lt;code&gt;wp_head&lt;/code&gt; at priority 10 – took 340ms&lt;/li&gt;
&lt;li&gt;Callback &lt;code&gt;AnotherPlugin::check_license()&lt;/code&gt; on hook &lt;code&gt;init&lt;/code&gt; at priority 5 – took 180ms (external HTTP call)&lt;/li&gt;
&lt;li&gt;Callback &lt;code&gt;ContactFormPlugin::enqueue_assets()&lt;/code&gt; on hook &lt;code&gt;wp_enqueue_scripts&lt;/code&gt; at priority 10 – took 45ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you’re looking at actionable data. You can see that the CSS generation callback is the biggest bottleneck. You can see that a license check is making an external HTTP request on every page load. You can trace each slow callback to its exact source file and line number.&lt;/p&gt;

&lt;p&gt;This is what &lt;a href="https://wpmultitool.com/" rel="noopener noreferrer"&gt;WP Multitool’s Find Slow Callbacks module&lt;/a&gt; does. You profile any page on your site, and it returns a ranked list of every callback by execution time, showing which plugin or theme each belongs to. No guessing, no disabling plugins one by one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hook Profiling vs. Query Monitor: When to Use Which
&lt;/h3&gt;

&lt;p&gt;They’re complementary tools, not competitors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Query Monitor when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You suspect database queries are the bottleneck (it groups queries by plugin and shows slow ones)&lt;/li&gt;
&lt;li&gt;You need to debug a specific page template or conditional logic&lt;/li&gt;
&lt;li&gt;You want to see which scripts and styles are loading unnecessarily&lt;/li&gt;
&lt;li&gt;You’re debugging REST API requests or AJAX calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use a hook profiler when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page load time is high but query count looks normal&lt;/li&gt;
&lt;li&gt;You need to find the &lt;em&gt;exact callback function&lt;/em&gt; that’s slow, not just the plugin&lt;/li&gt;
&lt;li&gt;You suspect PHP execution time is the problem, not database&lt;/li&gt;
&lt;li&gt;A plugin has many hooks and you need to know which specific one is the bottleneck&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my experience, the most effective approach is to start with a hook profile to find the slow callbacks, then use Query Monitor to dig into the database queries those callbacks are generating. Top-down, then bottom-up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Culprits: What Usually Causes Plugin Slowness
&lt;/h2&gt;

&lt;p&gt;After profiling hundreds of WordPress sites, certain patterns keep showing up. Here’s what to look for.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. External HTTP Requests on Every Page Load
&lt;/h3&gt;

&lt;p&gt;This is the number one performance killer that most people miss. A plugin makes an HTTP request to an external server – to check a license, fetch social counts, load remote data, or verify an API key. Each request adds 200-2000ms depending on the remote server’s response time.&lt;/p&gt;

&lt;p&gt;The worst part? These often happen on &lt;code&gt;init&lt;/code&gt; or &lt;code&gt;admin_init&lt;/code&gt;, meaning they fire on every single page load. Check Query Monitor’s HTTP API Calls panel – if you see external requests that aren’t cached, that’s your problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Plugins Loading Assets Everywhere
&lt;/h3&gt;

&lt;p&gt;A contact form plugin that loads its CSS and JavaScript on every page, not just the page with the form. A slider plugin loading its library on posts that don’t have sliders. This is incredibly common and adds up fast.&lt;/p&gt;

&lt;p&gt;Query Monitor’s Scripts and Styles panel makes this obvious. Look for plugin assets loading on pages where they shouldn’t be. The fix is usually a &lt;a href="https://makewpfast.com/how-to-properly-defer-javascript-in-wordpress/" rel="noopener noreferrer"&gt;conditional dequeue&lt;/a&gt; or switching to a plugin that handles this properly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Unoptimized Database Queries
&lt;/h3&gt;

&lt;p&gt;Some plugins run queries without proper indexing, or they query the entire &lt;code&gt;wp_options&lt;/code&gt; table with autoloaded data they don’t need on every page. I wrote about this specifically in the &lt;a href="https://makewpfast.com/wordpress-slow-queries/" rel="noopener noreferrer"&gt;slow queries guide&lt;/a&gt; – it’s a deep topic.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://makewpfast.com/wordpress-autoload-the-hidden-performance-killer/" rel="noopener noreferrer"&gt;autoload problem&lt;/a&gt; deserves special mention. Plugins that store large serialized arrays in wp_options with autoload=yes are silently adding to every single page load. WP Multitool’s Autoloader Optimizer module specifically addresses this.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Redundant Processing in wp_head
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;wp_head&lt;/code&gt; hook is where a lot of damage happens. Plugins generating inline CSS, computing critical styles, outputting meta tags that require database lookups – all of this runs before any visible content reaches the browser.&lt;/p&gt;

&lt;p&gt;Profile your homepage and sort by the &lt;code&gt;wp_head&lt;/code&gt; hook. You might be surprised how much time is spent there.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Plugin Conflicts Creating Duplicate Work
&lt;/h3&gt;

&lt;p&gt;Two SEO plugins both generating meta tags. Two caching plugins fighting each other. An optimization plugin and a CDN plugin both trying to rewrite URLs. These conflicts don’t just cause errors – they cause &lt;a href="https://makewpfast.com/wordpress-plugin-conflicts-how-to-diagnose-and-resolve-them/" rel="noopener noreferrer"&gt;slowness through redundant processing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Diagnostic Workflow
&lt;/h2&gt;

&lt;p&gt;Here’s the process I actually follow when someone tells me their WordPress site is slow and they want to find the plugin causing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Establish a Baseline
&lt;/h3&gt;

&lt;p&gt;Before touching anything, measure your current state. Open your browser’s DevTools, go to the Network tab, and do a hard refresh (Ctrl+Shift+R). Note the total page load time and the TTFB (Time to First Byte). TTFB is the important one – it tells you how long the server took to generate the page.&lt;/p&gt;

&lt;p&gt;If TTFB is under 200ms, your server-side performance is fine and the problem is likely frontend (too many scripts, unoptimized images). If TTFB is over 500ms, you have a backend problem – and that’s where plugins live.&lt;/p&gt;

&lt;p&gt;I outlined a more complete version of this in the &lt;a href="https://makewpfast.com/finding-what-makes-wordpress-slow/" rel="noopener noreferrer"&gt;diagnostic framework article&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Profile the Page
&lt;/h3&gt;

&lt;p&gt;Run a hook profile on the slow page. If you’re using WP Multitool, go to the Find Slow Callbacks module, enter the URL, and hit profile. You’ll get a ranked list of callbacks by execution time.&lt;/p&gt;

&lt;p&gt;Look at the top 5 callbacks. They typically account for 80% of the total execution time. Note which plugins they belong to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Investigate the Slow Callbacks
&lt;/h3&gt;

&lt;p&gt;Now you know which callbacks are slow. The question is why.&lt;/p&gt;

&lt;p&gt;Open Query Monitor and navigate to the page. Check the Queries by Component tab for the plugins you identified. Are they running expensive queries? Are they making external HTTP calls? Are they doing heavy PHP computation?&lt;/p&gt;

&lt;p&gt;This combination – hook profiler to find the slow callbacks, Query Monitor to understand why they’re slow – gives you the complete picture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Fix or Replace
&lt;/h3&gt;

&lt;p&gt;Once you know the exact callback and the reason it’s slow, you have options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Disable the feature&lt;/strong&gt; – if the plugin has a setting to turn off the slow functionality, use it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dequeue unnecessary assets&lt;/strong&gt; – if the plugin loads scripts/styles everywhere, conditionally remove them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add caching&lt;/strong&gt; – if a callback runs expensive queries, a transient cache might help.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace the plugin&lt;/strong&gt; – sometimes the plugin is just poorly written. Find an alternative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Report to the developer&lt;/strong&gt; – with specific callback and timing data, the developer can actually fix it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What About P3 (Plugin Performance Profiler)?
&lt;/h2&gt;

&lt;p&gt;You might see older articles recommending P3 Plugin Performance Profiler. Don’t use it. It hasn’t been updated since 2017, it’s not compatible with modern PHP versions, and it can actually crash your site. The WordPress plugin directory still lists it but it’s effectively abandoned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/code-profiler/" rel="noopener noreferrer"&gt;Code Profiler&lt;/a&gt; is a more modern alternative if you want a free standalone profiling plugin. It generates detailed charts showing execution time per file and function. The reports are comprehensive – maybe too comprehensive for a quick diagnosis, but great for deep dives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The “Is It Really a Plugin?” Check
&lt;/h2&gt;

&lt;p&gt;Before you spend hours hunting for a slow plugin, make sure the problem is actually a plugin. Sometimes slow WordPress sites are caused by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slow hosting&lt;/strong&gt; – if your TTFB is 2+ seconds with zero plugins, it’s not a plugin problem. It’s your server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Theme issues&lt;/strong&gt; – themes can be just as slow as plugins. The same diagnostic process applies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database bloat&lt;/strong&gt; – millions of post revisions, spam comments, expired transients. Check the &lt;a href="https://makewpfast.com/wordpress-database-bloat-cleaning-revisions-transients-and-spam/" rel="noopener noreferrer"&gt;database bloat guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No page caching&lt;/strong&gt; – without caching, WordPress regenerates every page from scratch. That’s slow by design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;wp-config.php misconfiguration&lt;/strong&gt; – wrong memory limits, debug mode left on in production. See the &lt;a href="https://makewpfast.com/the-complete-wp-config-php-performance-tuning-guide/" rel="noopener noreferrer"&gt;wp-config tuning guide&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your &lt;a href="https://makewpfast.com/why-your-wordpress-admin-dashboard-is-so-slow/" rel="noopener noreferrer"&gt;admin dashboard is specifically slow&lt;/a&gt;, that’s a different diagnostic path – admin-only plugins and dashboard widgets are often the cause there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;Finding which plugin is slowing down your WordPress site doesn’t have to be a guessing game. The disable-one-by-one method works but it’s slow, disruptive, and doesn’t give you the full picture.&lt;/p&gt;

&lt;p&gt;Better approach: profile first, investigate second, fix third. A hook profiler tells you which callbacks are slow. Query Monitor tells you why. Together they give you precise, actionable information instead of trial and error.&lt;/p&gt;

&lt;p&gt;The number of plugins you have installed matters way less than the quality of those plugins. I’ve seen sites with 40 plugins loading in under a second, and sites with 8 plugins taking 4 seconds. It’s not about quantity – it’s about what each plugin actually does on every request.&lt;/p&gt;

&lt;p&gt;Profile your site. Look at the data. Fix what’s actually slow.&lt;/p&gt;

&lt;p&gt;And if you’d rather hand the whole process to someone who does this daily, a &lt;a href="https://marcindudek.dev/services/wp-performance-audit/" rel="noopener noreferrer"&gt;WordPress performance audit&lt;/a&gt; covers exactly these steps – profiling, query analysis, and a prioritized fix list.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>WordPress REST API Performance: Disable What You Don’t Need</title>
      <dc:creator>MakeWPFast</dc:creator>
      <pubDate>Sun, 21 Jun 2026 08:15:09 +0000</pubDate>
      <link>https://dev.to/make-wp-fast/wordpress-rest-api-performance-disable-what-you-dont-need-o2h</link>
      <guid>https://dev.to/make-wp-fast/wordpress-rest-api-performance-disable-what-you-dont-need-o2h</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://makewpfast.com/wordpress-rest-api-performance-disable-what-you-dont-need/" rel="noopener noreferrer"&gt;https://makewpfast.com/wordpress-rest-api-performance-disable-what-you-dont-need/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every WordPress request to &lt;code&gt;/wp-json/&lt;/code&gt; boots the entire REST API infrastructure. That means 40+ endpoint controllers get instantiated, routes get registered, and hooks fire – even if the request only needs one endpoint.&lt;/p&gt;

&lt;p&gt;Most WordPress sites don’t need 90% of these endpoints. But they’re all there, loaded and ready, on every single REST request. And on non-REST requests, WordPress still outputs REST discovery links in your HTML head and HTTP headers.&lt;/p&gt;

&lt;p&gt;Here’s what’s actually happening, which endpoints are safe to remove, and how to audit what’s hitting your REST API.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the REST API Loads on Every Request
&lt;/h2&gt;

&lt;p&gt;Even on regular frontend page loads, WordPress adds two things related to the REST API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A `&lt;code&gt;tag in&lt;/code&gt;wp_head&lt;code&gt;via&lt;/code&gt;rest_output_link_wp_head` (priority 10)&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Link:&lt;/code&gt; HTTP header via &lt;code&gt;rest_output_link_header&lt;/code&gt; on &lt;code&gt;template_redirect&lt;/code&gt; (priority 11)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are minor. The real cost comes when something actually hits &lt;code&gt;/wp-json/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When a REST request comes in, WordPress calls &lt;code&gt;rest_api_loaded()&lt;/code&gt; from &lt;code&gt;parse_request&lt;/code&gt;, which triggers &lt;code&gt;rest_get_server()&lt;/code&gt; and fires the &lt;code&gt;rest_api_init&lt;/code&gt; hook. Inside that hook, &lt;code&gt;create_initial_rest_routes()&lt;/code&gt; runs at priority 99 and registers every single default endpoint controller.&lt;/p&gt;

&lt;p&gt;I counted them in WordPress 6.7 core. Here’s the full list of controllers that get instantiated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Posts, pages, and every custom post type with &lt;code&gt;show_in_rest =&amp;gt; true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Revisions and autosaves for each of those&lt;/li&gt;
&lt;li&gt;Post types and post statuses&lt;/li&gt;
&lt;li&gt;Taxonomies and terms&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Users and application passwords&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Comments&lt;/li&gt;
&lt;li&gt;Search (posts, terms, post formats)&lt;/li&gt;
&lt;li&gt;Block renderer, block types, blocks&lt;/li&gt;
&lt;li&gt;Settings&lt;/li&gt;
&lt;li&gt;Themes and plugins&lt;/li&gt;
&lt;li&gt;Sidebars, widget types, widgets&lt;/li&gt;
&lt;li&gt;Block directory and pattern directory&lt;/li&gt;
&lt;li&gt;Block patterns and block pattern categories&lt;/li&gt;
&lt;li&gt;Site health&lt;/li&gt;
&lt;li&gt;URL details&lt;/li&gt;
&lt;li&gt;Menu locations&lt;/li&gt;
&lt;li&gt;Site editor export&lt;/li&gt;
&lt;li&gt;Navigation fallback&lt;/li&gt;
&lt;li&gt;Font collections&lt;/li&gt;
&lt;li&gt;Abilities (categories, run, list)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s 40+ controller classes. Each one calls &lt;code&gt;register_routes()&lt;/code&gt;, which means regex patterns get compiled for every route. On a site with WooCommerce, Yoast, or any plugin that adds REST endpoints, you can easily hit 200+ registered routes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Security Problem: User Enumeration
&lt;/h2&gt;

&lt;p&gt;Here’s the part most people miss. By default, anyone can hit:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`php&lt;br&gt;
https://yoursite.com/wp-json/wp/v2/users&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And get back a JSON response with usernames, user IDs, and profile URLs. No authentication needed.&lt;/p&gt;

&lt;p&gt;This is a known attack vector – &lt;a href="https://www.acunetix.com/vulnerabilities/web/wordpress-rest-api-user-enumeration/" rel="noopener noreferrer"&gt;CVE-2017-5487&lt;/a&gt; originally, but the behavior still exists by design. Attackers use it to harvest usernames for brute-force login attempts. Combined with XML-RPC’s &lt;code&gt;system.multicall&lt;/code&gt; (which you should already have disabled – see our &lt;a href="https://makewpfast.com/xml-rpc-in-wordpress-the-security-and-performance-risk-you-should-disable/" rel="noopener noreferrer"&gt;XML-RPC security guide&lt;/a&gt;), it gives attackers everything they need to start password spraying.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Audit Which Endpoints Are Being Hit
&lt;/h2&gt;

&lt;p&gt;Before you start removing things, figure out what’s actually being used. There are a few ways to do this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method 1: Server Access Logs
&lt;/h3&gt;

&lt;p&gt;If you have access to your server logs, filter for &lt;code&gt;/wp-json/&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`php&lt;br&gt;
grep "wp-json" /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This shows you which REST endpoints get the most traffic. You might be surprised – on most sites, it’s the block editor endpoints and heartbeat-related calls from the admin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method 2: WordPress Debug Logging
&lt;/h3&gt;

&lt;p&gt;Add a quick logger to see REST requests in real time:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`php&lt;br&gt;
add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) {&lt;br&gt;
    if ( defined( 'WP_DEBUG_LOG' ) &amp;amp;&amp;amp; WP_DEBUG_LOG ) {&lt;br&gt;
        error_log( sprintf(&lt;br&gt;
            'REST API: %s %s (user: %d)',&lt;br&gt;
            $request-&amp;gt;get_method(),&lt;br&gt;
            $request-&amp;gt;get_route(),&lt;br&gt;
            get_current_user_id()&lt;br&gt;
        ) );&lt;br&gt;
    }&lt;br&gt;
    return $result;&lt;br&gt;
}, 10, 3 );&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Run this for a day, then check &lt;code&gt;wp-content/debug.log&lt;/code&gt;. You’ll see exactly what’s calling what.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method 3: Query Monitor
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://wordpress.org/plugins/query-monitor/" rel="noopener noreferrer"&gt;Query Monitor&lt;/a&gt; plugin shows REST API calls in its HTTP API panel. Good for development, but don’t run it in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Endpoints Are Safe to Disable
&lt;/h2&gt;

&lt;p&gt;This depends on your setup. Here’s my breakdown:&lt;/p&gt;

&lt;h3&gt;
  
  
  Safe to Remove on Most Sites
&lt;/h3&gt;

&lt;p&gt;These endpoints serve the block editor, widget system, and site editor. If you’re using a classic theme (like GeneratePress) and the classic editor, you don’t need them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/block-renderer&lt;/code&gt; – Block rendering&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/block-types&lt;/code&gt; – Block type definitions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/blocks&lt;/code&gt; – Reusable blocks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/block-directory&lt;/code&gt; – Block directory search&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/block-patterns&lt;/code&gt; – Block patterns&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/block-pattern-categories&lt;/code&gt; – Block pattern categories&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/sidebars&lt;/code&gt; – Widget sidebars&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/widget-types&lt;/code&gt; – Widget type definitions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/widgets&lt;/code&gt; – Widgets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/navigation&lt;/code&gt; – Navigation fallback&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/templates&lt;/code&gt; – Site editor templates&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/template-parts&lt;/code&gt; – Template parts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/global-styles&lt;/code&gt; – Global styles&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/menu-locations&lt;/code&gt; – Menu locations (REST)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/font-collections&lt;/code&gt; – Font collections&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/font-families&lt;/code&gt; – Font families&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Safe to Restrict to Authenticated Users
&lt;/h3&gt;

&lt;p&gt;These leak information publicly but are needed for admin functionality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/users&lt;/code&gt; – &lt;strong&gt;Disable for unauthenticated requests&lt;/strong&gt; (user enumeration)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/settings&lt;/code&gt; – Site settings&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/plugins&lt;/code&gt; – Plugin list&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/themes&lt;/code&gt; – Theme information&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/site-health&lt;/code&gt; – Site health data&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Don’t Touch These
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/posts&lt;/code&gt; and &lt;code&gt;/wp/v2/pages&lt;/code&gt; – Needed if anything reads your content via API&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/media&lt;/code&gt; – Image uploads from the editor&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/categories&lt;/code&gt; and &lt;code&gt;/wp/v2/tags&lt;/code&gt; – Taxonomy queries&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/comments&lt;/code&gt; – Comment system (if you use it)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp/v2/search&lt;/code&gt; – Site search&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Code: Removing Unnecessary Endpoints
&lt;/h2&gt;

&lt;p&gt;Here’s a practical mu-plugin that removes endpoints you don’t need. Drop this in &lt;code&gt;wp-content/mu-plugins/&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;php&lt;br&gt;
&amp;lt;?php&lt;br&gt;
/**&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plugin Name: Disable Unused REST API Endpoints&lt;/li&gt;
&lt;li&gt;Description: Removes REST API endpoints that aren't needed on this site.
*/&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;add_filter( 'rest_endpoints', function( $endpoints ) {&lt;br&gt;
    // Block editor endpoints (safe to remove with classic editor)&lt;br&gt;
    $remove_patterns = array(&lt;br&gt;
        '/wp/v2/block-renderer',&lt;br&gt;
        '/wp/v2/block-types',&lt;br&gt;
        '/wp/v2/blocks',&lt;br&gt;
        '/wp/v2/block-directory',&lt;br&gt;
        '/wp/v2/block-patterns',&lt;br&gt;
        '/wp/v2/block-pattern-categories',&lt;br&gt;
        '/wp/v2/sidebars',&lt;br&gt;
        '/wp/v2/widget-types',&lt;br&gt;
        '/wp/v2/widgets',&lt;br&gt;
        '/wp/v2/navigation',&lt;br&gt;
        '/wp/v2/templates',&lt;br&gt;
        '/wp/v2/template-parts',&lt;br&gt;
        '/wp/v2/global-styles',&lt;br&gt;
        '/wp/v2/menu-locations',&lt;br&gt;
        '/wp/v2/font-collections',&lt;br&gt;
        '/wp/v2/font-families',&lt;br&gt;
        '/wp/v2/font-faces',&lt;br&gt;
    );&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;foreach ( $endpoints as $route =&amp;gt; $endpoint ) {
    foreach ( $remove_patterns as $pattern ) {
        if ( strpos( $route, $pattern ) === 0 ) {
            unset( $endpoints[ $route ] );
            break;
        }
    }
}

return $endpoints;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;} );&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;h3&gt;
  
  
  Blocking User Enumeration
&lt;/h3&gt;

&lt;p&gt;This one’s non-negotiable. Restrict the users endpoint to authenticated requests:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`php&lt;br&gt;
add_filter( 'rest_endpoints', function( $endpoints ) {&lt;br&gt;
    if ( ! is_user_logged_in() ) {&lt;br&gt;
        if ( isset( $endpoints['/wp/v2/users'] ) ) {&lt;br&gt;
            unset( $endpoints['/wp/v2/users'] );&lt;br&gt;
        }&lt;br&gt;
        if ( isset( $endpoints['/wp/v2/users/(?P&amp;lt;id&amp;gt;[\d]+)'] ) ) {&lt;br&gt;
            unset( $endpoints['/wp/v2/users/(?P&amp;lt;id&amp;gt;[\d]+)'] );&lt;br&gt;
        }&lt;br&gt;
    }&lt;br&gt;
    return $endpoints;&lt;br&gt;
} );&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After adding this, test it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`php&lt;br&gt;
curl -s https://yoursite.com/wp-json/wp/v2/users | head -c 200&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should get a &lt;code&gt;rest_no_route&lt;/code&gt; error instead of a list of usernames.&lt;/p&gt;

&lt;h3&gt;
  
  
  Removing REST API Discovery Links
&lt;/h3&gt;

&lt;p&gt;If you don’t expose any public API, remove the discovery links from your HTML:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;php&lt;br&gt;
// Remove  from wp_head&lt;br&gt;
remove_action( 'wp_head', 'rest_output_link_wp_head', 10 );&lt;/p&gt;

&lt;p&gt;// Remove Link: header&lt;br&gt;
remove_action( 'template_redirect', 'rest_output_link_header', 11 );&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;These are in &lt;code&gt;wp-includes/default-filters.php&lt;/code&gt; at lines 331-332. Removing them saves a small amount of HTML output and stops advertising your API endpoint to scanners.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Nuclear Option: Require Authentication for Everything
&lt;/h2&gt;

&lt;p&gt;If your site doesn’t serve any public REST API consumers (no headless frontend, no mobile app, no external integrations), you can lock down the entire API:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;php&lt;br&gt;
add_filter( 'rest_authentication_errors', function( $result ) {&lt;br&gt;
    if ( true === $result || is_wp_error( $result ) ) {&lt;br&gt;
        return $result;&lt;br&gt;
    }&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if ( ! is_user_logged_in() ) {
    return new WP_Error(
        'rest_not_logged_in',
        'REST API access restricted.',
        array( 'status' =&amp;gt; 401 )
    );
}

return $result;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;} );&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: This breaks any plugin that makes unauthenticated REST calls from the frontend. Contact Form 7, some caching plugins, and various page builders use the REST API from the browser without authentication. Test thoroughly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About Performance Impact?
&lt;/h2&gt;

&lt;p&gt;I want to be honest here – the performance gain from removing REST endpoints on non-REST requests is zero. The &lt;code&gt;rest_api_init&lt;/code&gt; hook only fires when something actually hits &lt;code&gt;/wp-json/&lt;/code&gt;. On regular page loads, removing endpoints changes nothing.&lt;/p&gt;

&lt;p&gt;Where it matters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Admin pages&lt;/strong&gt; – The block editor makes dozens of REST calls. Fewer registered routes means slightly faster route matching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST-heavy plugins&lt;/strong&gt; – WooCommerce, Jetpack, and similar plugins add their own endpoints. The more routes registered, the longer route matching takes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security scanning&lt;/strong&gt; – Bots constantly probe &lt;code&gt;/wp-json/wp/v2/users&lt;/code&gt;. Returning a 401 early saves processing time compared to running the full users query.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The real win is security, not speed. But if you’re running a site that serves REST API responses at scale (headless WordPress, mobile app backend), trimming unused routes does reduce the regex matching overhead in &lt;code&gt;WP_REST_Server::dispatch()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring REST API Usage with WP Multitool
&lt;/h2&gt;

&lt;p&gt;If you’re using &lt;a href="https://wpmultitool.com/" rel="noopener noreferrer"&gt;WP Multitool&lt;/a&gt;, the Slow Query Analyzer and Find Slow Callbacks modules help you spot REST API-related performance issues. The Slow Query Analyzer catches expensive database queries triggered by REST endpoints, while Find Slow Callbacks profiles hooks – including those firing on &lt;code&gt;rest_api_init&lt;/code&gt;. This is particularly useful for finding plugins that register heavy callbacks on REST requests when they shouldn’t be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Checklist
&lt;/h2&gt;

&lt;p&gt;Before you deploy any of these changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Audit first&lt;/strong&gt; – Log REST requests for at least 24 hours before removing anything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the block editor&lt;/strong&gt; – If you use Gutenberg, don’t remove block-related endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check contact forms&lt;/strong&gt; – Many form plugins use REST API for submissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify after deployment&lt;/strong&gt; – Hit &lt;code&gt;/wp-json/&lt;/code&gt; and confirm removed endpoints are gone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor error logs&lt;/strong&gt; – Watch for &lt;code&gt;rest_no_route&lt;/code&gt; errors from legitimate functionality&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Related guides that complement this one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://makewpfast.com/the-complete-wp-config-php-performance-tuning-guide/" rel="noopener noreferrer"&gt;The Complete wp-config.php Performance Tuning Guide&lt;/a&gt; covers &lt;code&gt;WP_DEBUG_LOG&lt;/code&gt; setup and other performance constants&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://makewpfast.com/how-to-properly-defer-javascript-in-wordpress/" rel="noopener noreferrer"&gt;How to Properly Defer JavaScript in WordPress&lt;/a&gt; – because the REST API isn’t the only thing loading unnecessary resources&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://makewpfast.com/xml-rpc-in-wordpress-the-security-and-performance-risk-you-should-disable/" rel="noopener noreferrer"&gt;XML-RPC in WordPress&lt;/a&gt; – the other legacy endpoint you should disable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The REST API isn’t going anywhere – WordPress core depends on it. But there’s no reason to serve 200+ routes when your site only uses 10 of them. Trim the fat, lock down the users endpoint, and monitor what’s actually being requested. That’s the pragmatic approach.&lt;/p&gt;

</description>
      <category>api</category>
      <category>performance</category>
      <category>php</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Object Cache in WordPress: Redis vs Memcached vs Nothing</title>
      <dc:creator>MakeWPFast</dc:creator>
      <pubDate>Sat, 20 Jun 2026 08:15:12 +0000</pubDate>
      <link>https://dev.to/make-wp-fast/object-cache-in-wordpress-redis-vs-memcached-vs-nothing-13gl</link>
      <guid>https://dev.to/make-wp-fast/object-cache-in-wordpress-redis-vs-memcached-vs-nothing-13gl</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://makewpfast.com/wordpress-object-cache-redis-vs-memcached/" rel="noopener noreferrer"&gt;https://makewpfast.com/wordpress-object-cache-redis-vs-memcached/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every WordPress page load runs dozens of database queries. Options, user meta, transients, post data – the same stuff gets fetched over and over. That’s where the WordPress object cache comes in. It stores query results in memory so WordPress doesn’t have to hit the database every single time.&lt;/p&gt;

&lt;p&gt;But here’s what most “Redis vs Memcached” articles get wrong – they jump straight to the comparison without explaining what the object cache actually does. So let’s fix that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the WordPress Object Cache Actually Does
&lt;/h2&gt;

&lt;p&gt;WordPress has a built-in object cache. It’s been there since version 2.5. Every time WordPress fetches an option, loads post meta, or grabs user data, it stores the result in a PHP array using &lt;code&gt;wp_cache_set()&lt;/code&gt;. The next time that same data is needed during the request, it calls &lt;code&gt;wp_cache_get()&lt;/code&gt; and grabs it from memory instead of running another SQL query.&lt;/p&gt;

&lt;p&gt;Here’s the thing most people miss – &lt;strong&gt;the default object cache only lives for one request&lt;/strong&gt;. When the page finishes loading, all that cached data is gone. The next visitor triggers the same queries all over again.&lt;/p&gt;

&lt;p&gt;That’s the default behavior. No plugin needed. WordPress does it out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where It Gets Interesting
&lt;/h3&gt;

&lt;p&gt;A persistent object cache changes the game. Instead of storing data in a PHP array that dies with the request, it stores it in an external memory store – like Redis or Memcached. Now that cached data survives between requests. Visitor A loads the homepage and populates the cache. Visitor B gets the cached version without touching the database at all.&lt;/p&gt;

&lt;p&gt;The mechanism is a WordPress “drop-in” – a special file called &lt;code&gt;object-cache.php&lt;/code&gt; placed in &lt;code&gt;wp-content/&lt;/code&gt;. WordPress checks for this file on every request. If it exists, WordPress uses it instead of the default &lt;code&gt;WP_Object_Cache&lt;/code&gt; class. That’s how Redis or Memcached plugins hook into the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  When You Actually Need a Persistent Object Cache
&lt;/h2&gt;

&lt;p&gt;Not every site needs one. Here’s when it matters:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You probably need it if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your site runs WooCommerce or a membership plugin with logged-in users (page cache doesn’t help much for authenticated requests)&lt;/li&gt;
&lt;li&gt;You have a high-traffic site with complex queries – especially dynamic pages that can’t be page-cached&lt;/li&gt;
&lt;li&gt;Your &lt;a href="https://makewpfast.com/why-your-wordpress-admin-dashboard-is-so-slow/" rel="noopener noreferrer"&gt;admin dashboard is slow&lt;/a&gt; – object cache dramatically speeds up the backend&lt;/li&gt;
&lt;li&gt;You’re seeing hundreds of database queries per page load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You probably don’t need it if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You run a simple blog with mostly anonymous visitors and a page cache plugin&lt;/li&gt;
&lt;li&gt;Your hosting doesn’t support Redis or Memcached (shared hosting often doesn’t)&lt;/li&gt;
&lt;li&gt;You have fewer than 1,000 daily visitors and no complex plugins&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight is that object cache complements page cache – it doesn’t replace it. Page cache serves complete HTML pages. Object cache reduces database queries for content that can’t be page-cached. If you’re curious about what’s actually slowing down your queries, check out &lt;a href="https://makewpfast.com/wordpress-slow-queries/" rel="noopener noreferrer"&gt;how to find and fix slow WordPress queries&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redis vs Memcached: The Real Differences
&lt;/h2&gt;

&lt;p&gt;Ok, the comparison everyone came for. I’m not going to fabricate benchmark numbers – the raw speed difference between Redis and Memcached is negligible for WordPress object caching. Both operate in microseconds. What matters are the architectural differences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Persistence
&lt;/h3&gt;

&lt;p&gt;Redis can persist data to disk. If your Redis server restarts, the cache can be restored from the last snapshot. Memcached is purely in-memory – restart it and everything is gone.&lt;/p&gt;

&lt;p&gt;For WordPress, this matters less than you’d think. A cold cache rebuilds itself within a few page loads. But if you’re running a large WooCommerce store with millions of cached keys, a warm restart is nice to have.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Structures
&lt;/h3&gt;

&lt;p&gt;Redis supports strings, lists, sets, sorted sets, hashes, and more. Memcached is strictly key-value with string values.&lt;/p&gt;

&lt;p&gt;For basic WordPress object caching, this difference is mostly irrelevant – WordPress stores serialized PHP data as strings either way. But if you’re building custom functionality that uses the cache directly, Redis gives you more flexibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory Management
&lt;/h3&gt;

&lt;p&gt;Memcached uses a slab allocator. It pre-allocates memory in chunks and assigns items to the closest matching slab size. This is efficient but can waste memory when your cached items vary wildly in size.&lt;/p&gt;

&lt;p&gt;Redis allocates memory on demand using standard system allocators (jemalloc by default). More flexible, but can fragment over time.&lt;/p&gt;

&lt;p&gt;Both let you set a maximum memory limit. Both evict old keys when they hit the limit. Redis gives you more eviction policies to choose from (LRU, LFU, random, volatile-based).&lt;/p&gt;

&lt;h3&gt;
  
  
  Replication and High Availability
&lt;/h3&gt;

&lt;p&gt;Redis has built-in replication. You can set up Redis Sentinel for automatic failover or use Redis Cluster for horizontal scaling. Memcached has no native replication – you’d need external tools.&lt;/p&gt;

&lt;p&gt;For most WordPress sites, this is overkill. But if you’re running a high-availability setup behind a load balancer, Redis makes the infrastructure simpler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability
&lt;/h3&gt;

&lt;p&gt;Redis wins here. You can inspect keys, check memory usage per key, monitor commands in real-time with &lt;code&gt;MONITOR&lt;/code&gt;, and get detailed stats with &lt;code&gt;INFO&lt;/code&gt;. Memcached’s &lt;code&gt;stats&lt;/code&gt; command gives you the basics but you can’t easily peek at individual keys or their sizes.&lt;/p&gt;

&lt;p&gt;When you’re debugging why your object cache is eating 512MB of RAM, Redis makes the investigation much easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  WordPress Plugin Ecosystem
&lt;/h3&gt;

&lt;p&gt;Redis has better WordPress plugin support. &lt;a href="https://objectcache.pro/docs/debugging" rel="noopener noreferrer"&gt;Object Cache Pro&lt;/a&gt; (paid) and Redis Object Cache (free) are both mature and well-maintained. For Memcached, the options are more limited and less actively developed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Redis&lt;/th&gt;
&lt;th&gt;Memcached&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data persistence&lt;/td&gt;
&lt;td&gt;Yes (RDB/AOF)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data structures&lt;/td&gt;
&lt;td&gt;Strings, lists, sets, hashes, etc.&lt;/td&gt;
&lt;td&gt;Strings only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replication&lt;/td&gt;
&lt;td&gt;Built-in (Sentinel, Cluster)&lt;/td&gt;
&lt;td&gt;None (needs external tools)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory efficiency&lt;/td&gt;
&lt;td&gt;Good, can fragment&lt;/td&gt;
&lt;td&gt;Good, slab allocator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max memory eviction&lt;/td&gt;
&lt;td&gt;6 policies (LRU, LFU, etc.)&lt;/td&gt;
&lt;td&gt;LRU only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key inspection&lt;/td&gt;
&lt;td&gt;Full (SCAN, DEBUG OBJECT)&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-threaded&lt;/td&gt;
&lt;td&gt;Single-threaded (I/O threads in 6.0+)&lt;/td&gt;
&lt;td&gt;Multi-threaded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WordPress plugins&lt;/td&gt;
&lt;td&gt;Excellent (Object Cache Pro, Redis Object Cache)&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  My Recommendation
&lt;/h2&gt;

&lt;p&gt;Use Redis. Not because it’s faster for basic key-value operations – it’s not meaningfully different. But because the ecosystem is better, the debugging tools are better, and the WordPress plugin options are better maintained.&lt;/p&gt;

&lt;p&gt;The only scenario where I’d pick Memcached is if it’s already running and working on your server and you don’t want to touch the infrastructure. If you’re setting up from scratch, Redis is the way to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Verify Your Object Cache Is Working
&lt;/h2&gt;

&lt;p&gt;Installing a Redis plugin and hoping for the best isn’t enough. You need to verify it’s actually doing something.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the Drop-In Status
&lt;/h3&gt;

&lt;p&gt;In WP admin, go to &lt;strong&gt;Plugins &amp;gt; Drop-ins&lt;/strong&gt;. You should see &lt;code&gt;object-cache.php&lt;/code&gt; listed there. If it’s missing, the persistent cache isn’t active – regardless of what your caching plugin says.&lt;/p&gt;

&lt;p&gt;You can also check via WP-CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="n"&gt;wp&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should return something like &lt;code&gt;Redis&lt;/code&gt; or &lt;code&gt;Memcached&lt;/code&gt;. If it says &lt;code&gt;WP_Object_Cache&lt;/code&gt;, you’re running the default non-persistent cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check Hit Rates
&lt;/h3&gt;

&lt;p&gt;A healthy object cache should have a hit rate above 85%. If you’re seeing lower numbers, something is wrong – either the cache is too small, keys are expiring too fast, or a plugin is flushing the cache constantly.&lt;/p&gt;

&lt;p&gt;With Redis, you can check from the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cli&lt;/span&gt; &lt;span class="no"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;grep&lt;/span&gt; &lt;span class="n"&gt;keyspace&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;keyspace_hits&lt;/code&gt; and &lt;code&gt;keyspace_misses&lt;/code&gt; numbers tell you the real story. Calculate: &lt;code&gt;hits / (hits + misses) * 100&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use WP Multitool’s Cache Analyzer
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://wpmultitool.com/" rel="noopener noreferrer"&gt;WP Multitool&lt;/a&gt; includes a cache analyzer that shows you exactly what’s being cached, how much memory each group uses, and your hit/miss ratio. It’s useful for finding plugins that abuse the object cache or store way too much data. It also flags when your &lt;a href="https://makewpfast.com/wordpress-autoload-the-hidden-performance-killer/" rel="noopener noreferrer"&gt;autoloaded options are filling up the cache&lt;/a&gt; with data that shouldn’t be there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Gotchas
&lt;/h2&gt;

&lt;p&gt;I’ve seen these issues come up over and over. Save yourself the debugging.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The object-cache.php Conflict
&lt;/h3&gt;

&lt;p&gt;Only one &lt;code&gt;object-cache.php&lt;/code&gt; drop-in can exist at a time. If you install Redis Object Cache but you already have a Memcached drop-in, or if a managed hosting provider has their own drop-in, you’ll get conflicts. The &lt;a href="https://github.com/WordPress/performance/issues/612" rel="noopener noreferrer"&gt;WordPress Performance Lab plugin has documented this issue&lt;/a&gt; – when multiple plugins try to manage the same drop-in file, things break silently.&lt;/p&gt;

&lt;p&gt;Before installing any object cache plugin, check if &lt;code&gt;wp-content/object-cache.php&lt;/code&gt; already exists and who put it there.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Memory Limits Too Low
&lt;/h3&gt;

&lt;p&gt;The default Redis &lt;code&gt;maxmemory&lt;/code&gt; is often 64MB. For a WooCommerce store or a site with lots of plugins, that fills up fast. When it does, Redis starts evicting keys, your hit rate tanks, and you’re back to hammering the database.&lt;/p&gt;

&lt;p&gt;Set a realistic memory limit. For most WordPress sites, 128-256MB is a good starting point. Monitor usage and adjust.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. SAVEQUERIES and WP_DEBUG in Production
&lt;/h3&gt;

&lt;p&gt;This is a sneaky one. If &lt;code&gt;WP_DEBUG&lt;/code&gt; or &lt;code&gt;SAVEQUERIES&lt;/code&gt; is enabled, Object Cache Pro and similar plugins will store backtraces for every cache operation. This is incredibly memory-intensive and can cause PHP fatal errors from memory exhaustion. Never run these in production.&lt;/p&gt;

&lt;p&gt;Check your &lt;a href="https://makewpfast.com/the-complete-wp-config-php-performance-tuning-guide/" rel="noopener noreferrer"&gt;wp-config.php&lt;/a&gt; – make sure both are &lt;code&gt;false&lt;/code&gt; on production.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Plugins Flushing the Cache Constantly
&lt;/h3&gt;

&lt;p&gt;Some plugins call &lt;code&gt;wp_cache_flush()&lt;/code&gt; way too aggressively. Every time the cache is flushed, everything has to be rebuilt from the database. I’ve seen plugins that flush the entire object cache on every post save.&lt;/p&gt;

&lt;p&gt;If your cache hit rate is suspiciously low, this is likely the cause. You can monitor Redis commands to catch the culprit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cli&lt;/span&gt; &lt;span class="no"&gt;MONITOR&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;grep&lt;/span&gt; &lt;span class="no"&gt;FLUSHDB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then trace back which plugin is triggering it.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Transients Behavior Changes
&lt;/h3&gt;

&lt;p&gt;Here’s something most people don’t know – when you install a persistent object cache, &lt;a href="https://makewpfast.com/wordpress-database-bloat-cleaning-revisions-transients-and-spam/" rel="noopener noreferrer"&gt;WordPress stops storing transients in the database&lt;/a&gt;. Instead, they go to the object cache. This is usually a good thing (less database bloat), but it means transients are now subject to cache eviction. If you have plugins that rely on transients persisting for days, they might break when Redis evicts them under memory pressure.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Serialization Overhead
&lt;/h3&gt;

&lt;p&gt;WordPress serializes every object before storing it in the cache. For large objects – like a complex WP_Query result with 500 posts – the serialization/unserialization cost can actually be higher than just running the query again. This is rare, but worth knowing if you’re caching custom data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The “Do Nothing” Option
&lt;/h2&gt;

&lt;p&gt;Here’s the part most articles skip. Sometimes the best object cache is no persistent object cache at all.&lt;/p&gt;

&lt;p&gt;The default WordPress object cache still works within each request. If your site is mostly anonymous visitors with a page cache serving 95% of requests, adding Redis complexity might not be worth it. The pages that bypass the page cache (admin, AJAX, WP-Cron) might not generate enough database load to justify another service in your stack.&lt;/p&gt;

&lt;p&gt;Start by looking at your actual database query count and load. If you’re seeing 50-80 queries per page and your database server isn’t stressed, you might not need Redis at all. Focus on &lt;a href="https://makewpfast.com/wordpress-database-optimization/" rel="noopener noreferrer"&gt;optimizing your database&lt;/a&gt; first – clean up bloated tables, fix missing indexes, reduce autoloaded data.&lt;/p&gt;

&lt;p&gt;Object cache is a tool, not a requirement. Use it when the data tells you it’s needed.&lt;/p&gt;

</description>
      <category>database</category>
      <category>performance</category>
      <category>php</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>WordPress Multisite Performance: Scaling to 50+ Sites Without Dying</title>
      <dc:creator>MakeWPFast</dc:creator>
      <pubDate>Sat, 20 Jun 2026 08:15:11 +0000</pubDate>
      <link>https://dev.to/make-wp-fast/wordpress-multisite-performance-scaling-to-50-sites-without-dying-535b</link>
      <guid>https://dev.to/make-wp-fast/wordpress-multisite-performance-scaling-to-50-sites-without-dying-535b</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://makewpfast.com/wordpress-multisite-performance-scaling/" rel="noopener noreferrer"&gt;https://makewpfast.com/wordpress-multisite-performance-scaling/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you’re running a WordPress multisite with 20+ client sites and wondering why the admin dashboard takes 8 seconds to load – yeah, I’ve been there. Multisite is one of those WordPress features that sounds incredible in theory and slowly crushes your server in practice.&lt;/p&gt;

&lt;p&gt;The problem isn’t multisite itself. It’s that nobody tells you how the database architecture actually works under the hood, and by the time you figure it out, you’ve got 40 client sites on a single MySQL instance and your agency’s reputation is taking hits.&lt;/p&gt;

&lt;p&gt;This guide covers what actually matters when scaling WordPress multisite for agency use – database architecture, plugin activation strategy, object caching, domain mapping overhead, and the specific bottlenecks that show up around the 30-50 site mark.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Multisite Database Architecture Actually Works
&lt;/h2&gt;

&lt;p&gt;Before optimizing anything, you need to understand what WordPress does to your database when you add a site to the network.&lt;/p&gt;

&lt;p&gt;WordPress multisite uses a shared database with per-site table prefixes. Every time you create a new subsite, WordPress generates a full set of tables for that site with a numeric prefix. Site 2 gets &lt;code&gt;wp_2_posts&lt;/code&gt;, &lt;code&gt;wp_2_postmeta&lt;/code&gt;, &lt;code&gt;wp_2_options&lt;/code&gt;, &lt;code&gt;wp_2_comments&lt;/code&gt;, and so on. Site 47 gets &lt;code&gt;wp_47_posts&lt;/code&gt;, &lt;code&gt;wp_47_postmeta&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;Each site generates roughly 9-12 tables depending on active plugins. So at 50 sites, you’re looking at 500-600 tables in a single MySQL database. At 100 sites, over 1,000 tables.&lt;/p&gt;

&lt;p&gt;But here’s the part that catches people off guard – some tables are shared across the entire network:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;wp_users&lt;/code&gt; and &lt;code&gt;wp_usermeta&lt;/code&gt; – all user accounts across all sites&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wp_blogs&lt;/code&gt; – registry of every site in the network&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wp_site&lt;/code&gt; and &lt;code&gt;wp_sitemeta&lt;/code&gt; – network-level configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wp_registration_log&lt;/code&gt; and &lt;code&gt;wp_signups&lt;/code&gt; – signup data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is logical isolation, not physical isolation. Every site’s data lives in the same MySQL instance, sharing the same connection pool, the same query optimizer, and the same InnoDB buffer pool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shared Tables Problem
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;wp_users&lt;/code&gt; and &lt;code&gt;wp_usermeta&lt;/code&gt; tables are the first bottleneck you’ll hit at scale.&lt;/p&gt;

&lt;p&gt;Every site in the network shares these tables. If you’re an agency managing 50 client sites with 20 users each, that’s 1,000 users in &lt;code&gt;wp_users&lt;/code&gt; and potentially 50,000+ rows in &lt;code&gt;wp_usermeta&lt;/code&gt;. Each user gets capability metadata per site they belong to – &lt;code&gt;wp_2_capabilities&lt;/code&gt;, &lt;code&gt;wp_3_capabilities&lt;/code&gt;, etc., all stored as rows in the same &lt;code&gt;wp_usermeta&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;Run this query on your multisite to see the damage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="nb"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wp_usermeta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="nb"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wp_usermeta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;wp_usermeta&lt;/code&gt; is over 100K rows, you’re already in territory where user-related queries on the network admin screen start to lag. WordPress knows this too – the &lt;a href="https://developer.wordpress.org/reference/functions/wp_is_large_network/" rel="noopener noreferrer"&gt;wp_is_large_network()&lt;/a&gt; function kicks in at 10,000 users and defers real-time user counts to background cron jobs instead of calculating them on every admin page load.&lt;/p&gt;

&lt;p&gt;For agencies, the fix is straightforward: don’t add users to sites they don’t need access to. Sounds obvious, but I’ve seen multisite networks where every client admin was added to every site “just in case.”&lt;/p&gt;

&lt;h2&gt;
  
  
  The wp_options Autoload Trap (Multiplied by 50)
&lt;/h2&gt;

&lt;p&gt;If you’ve read about &lt;a href="https://makewpfast.com/wordpress-autoload-the-hidden-performance-killer/" rel="noopener noreferrer"&gt;WordPress autoload being a hidden performance killer&lt;/a&gt;, you already know the problem. On multisite, it’s that problem multiplied by every site in your network.&lt;/p&gt;

&lt;p&gt;Each site has its own &lt;code&gt;wp_{n}_options&lt;/code&gt; table, and each one loads all &lt;code&gt;autoload='yes'&lt;/code&gt; rows on every request to that site. A plugin that dumps 500KB of serialized data into autoloaded options is bad on a single site. On multisite with 50 sites, that same plugin is creating that overhead independently on each site.&lt;/p&gt;

&lt;p&gt;The tricky part – network-activated plugins write their settings to every site’s options table when they initialize. If you network-activate a plugin that stores large option values, you’ve just added that bloat to every site in one click.&lt;/p&gt;

&lt;p&gt;Check your per-site autoload size:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="nf"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp_'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blog_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'_options'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;blog_id&lt;/span&gt;
&lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wp_blogs&lt;/span&gt;
&lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;archived&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;deleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then for any specific site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="nf"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;option_value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;autoload_bytes&lt;/span&gt;
&lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wp_2_options&lt;/span&gt;
&lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;autoload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'yes'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any site is over 1MB of autoloaded data, that’s a problem. Over 2MB and you’ll feel it on every page load. I’ve written more about &lt;a href="https://makewpfast.com/wordpress-database-bloat-cleaning-revisions-transients-and-spam/" rel="noopener noreferrer"&gt;cleaning up database bloat&lt;/a&gt; – the same techniques apply per-site on multisite, you just need to do it across all sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network Activate vs. Per-Site Activate – It Actually Matters
&lt;/h2&gt;

&lt;p&gt;This is where most agencies get lazy, and it costs them.&lt;/p&gt;

&lt;p&gt;Network-activating a plugin means it runs on every single site in the network. The Super Admin enables it globally, and individual Site Admins can’t deactivate it. Convenient? Sure. But it means every page load on every site processes that plugin’s code, fires its hooks, runs its init routines – even on sites that don’t need it.&lt;/p&gt;

&lt;p&gt;The better approach for agencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network activate&lt;/strong&gt;: Security plugins, object cache drop-in, essential mu-plugins, your agency’s maintenance plugin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-site activate&lt;/strong&gt;: WooCommerce (only on commerce sites), page builders, contact form plugins, anything site-specific&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If only 5 out of 50 sites need WooCommerce, network-activating it means 45 sites are loading WooCommerce’s entire bootstrap on every request for no reason. WooCommerce alone adds 10-20 extra database queries per page load and consumes additional PHP memory.&lt;/p&gt;

&lt;p&gt;For must-use plugins that should be network-wide but lightweight, put them in &lt;code&gt;wp-content/mu-plugins/&lt;/code&gt;. These load before regular plugins and can’t be deactivated through the admin – perfect for agency infrastructure code.&lt;/p&gt;

&lt;h2&gt;
  
  
  switch_to_blog() – The Silent Performance Killer
&lt;/h2&gt;

&lt;p&gt;If you’re writing custom code for your multisite network, this is the function that’ll trip you up.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;switch_to_blog()&lt;/code&gt; lets you temporarily switch context to another site to access its data. Sounds handy. The problem is what happens under the hood – WordPress reloads the target site’s options into memory, clears relevant object cache entries, and reinitializes taxonomies and post types. Then &lt;code&gt;restore_current_blog()&lt;/code&gt; does it all again in reverse.&lt;/p&gt;

&lt;p&gt;One switch is fine. A loop switching through 50 sites? That’s been benchmarked at over 2 seconds without caching. At 100 sites, you’re looking at delays that make the operation impractical for any real-time request.&lt;/p&gt;

&lt;p&gt;This matters for agencies because common tasks trigger these loops – generating cross-site reports, checking plugin versions across the network, or displaying “recent posts from all sites” widgets. If your network admin dashboard is &lt;a href="https://makewpfast.com/why-your-wordpress-admin-dashboard-is-so-slow/" rel="noopener noreferrer"&gt;painfully slow&lt;/a&gt;, switch_to_blog loops are likely the cause.&lt;/p&gt;

&lt;p&gt;The fix: use direct database queries instead of switching context. If you need data from &lt;code&gt;wp_15_posts&lt;/code&gt;, just query it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;"SELECT ID, post_title FROM &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;base_prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;15_posts
     WHERE post_status = 'publish'
     ORDER BY post_date DESC
     LIMIT 10"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not as elegant as using the WordPress API, but at 50+ sites, elegance takes a back seat to page load times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain Mapping and sunrise.php Overhead
&lt;/h2&gt;

&lt;p&gt;If you’re running client sites on multisite, each client probably wants their own domain. That means domain mapping.&lt;/p&gt;

&lt;p&gt;Good news – since WordPress 4.5, domain mapping is native. You don’t need a plugin for it. Just go to Network Admin &amp;gt; Sites, edit the site, and change the Site Address to the custom domain. Make sure DNS points to your server and SSL is configured.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;sunrise.php&lt;/code&gt; drop-in is where things get interesting from a performance perspective. This file executes extremely early in the WordPress loading sequence – before mu-plugins, before regular plugins, before the theme. It runs on every single request to your network.&lt;/p&gt;

&lt;p&gt;The key constraint: &lt;code&gt;sunrise.php&lt;/code&gt; can’t access the database or most WordPress functions. It’s limited to pure PHP and setting constants. This is by design – executing expensive operations at this stage would slow down every request across every site.&lt;/p&gt;

&lt;p&gt;If you’re using a legacy domain mapping plugin that still relies on &lt;code&gt;sunrise.php&lt;/code&gt; (like the old WordPress MU Domain Mapping plugin), check what it’s actually doing in there. Some older implementations made database queries in sunrise.php, which is a performance hit on every request. With native domain mapping in modern WordPress, you can often remove &lt;code&gt;sunrise.php&lt;/code&gt; entirely.&lt;/p&gt;

&lt;p&gt;Add to your &lt;code&gt;wp-config.php&lt;/code&gt; if you need sunrise.php:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'SUNRISE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you’re getting cookie issues after domain mapping, this helps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'COOKIE_DOMAIN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'HTTP_HOST'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’ve covered more wp-config.php settings in the &lt;a href="https://makewpfast.com/the-complete-wp-config-php-performance-tuning-guide/" rel="noopener noreferrer"&gt;complete wp-config.php performance tuning guide&lt;/a&gt; – some of those settings are especially important on multisite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Object Caching – Non-Negotiable at Scale
&lt;/h2&gt;

&lt;p&gt;Running multisite with 20+ sites without Redis or Memcached is asking for trouble. The math is simple – each site generates its own set of database queries for options, posts, transients, and user data. Multiply that by the number of active sites and concurrent visitors, and your MySQL server is doing way more work than it needs to.&lt;/p&gt;

&lt;p&gt;Redis object caching on multisite can reduce database queries by 50-80%. In one IEEE study, the performance difference was significant enough that researchers found MySQL CPU usage dropped by 25% and network traffic by up to 94% when proper object caching was in place.&lt;/p&gt;

&lt;p&gt;There’s a catch with multisite though – cache isolation. By default, all sites in a network share the same Redis database. When you flush the cache (which plugins love to do), it clears the cache for every site, not just the one that triggered it.&lt;/p&gt;

&lt;p&gt;You can isolate per-site caches using unique key prefixes or separate Redis database numbers. If you’re using the Redis Object Cache plugin, set this in &lt;code&gt;wp-config.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WP_REDIS_PREFIX'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'site_'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;get_current_blog_id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;':'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or better – just use &lt;code&gt;WP_CACHE_KEY_SALT&lt;/code&gt; which most object cache implementations respect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WP_CACHE_KEY_SALT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'mynetwork_'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The salt gets combined with the blog ID automatically, so each site already gets isolated cache keys. What you want to prevent is one plugin’s aggressive cache flush wiping out cached data for all 50 sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Scaling Beyond 50 Sites
&lt;/h2&gt;

&lt;p&gt;At 50+ sites, your single MySQL instance is handling 500+ tables, shared user queries, and concurrent requests from multiple sites. Here’s when you need to think about scaling the database layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read replicas&lt;/strong&gt;: Route read queries (which are 90%+ of WordPress queries) to replica servers. The HyperDB or LudicrousDB drop-ins handle this – they’re database abstraction layers that route queries to different servers based on the table being accessed. This is a real option for agencies running 50-100 sites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query monitoring&lt;/strong&gt;: Enable &lt;a href="https://makewpfast.com/wordpress-slow-queries/" rel="noopener noreferrer"&gt;slow query logging&lt;/a&gt; on your MySQL server. On multisite, you’ll often find that the slowest queries come from &lt;code&gt;wp_usermeta&lt;/code&gt; JOINs and &lt;code&gt;wp_options&lt;/code&gt; autoload queries – the shared tables and per-site options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table optimization&lt;/strong&gt;: Run &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; on your largest per-site tables periodically. With hundreds of tables, this needs to be scripted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c1"&gt;# Optimize all WordPress multisite tables&lt;/span&gt;
&lt;span class="n"&gt;mysql&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;your_database&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="s2"&gt;"
SELECT CONCAT('OPTIMIZE TABLE \`', TABLE_NAME, '\`;')
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_database'
AND TABLE_NAME LIKE 'wp\_%'
AND DATA_FREE &amp;gt; 1048576
"&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;skip&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;mysql&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;your_database&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This only optimizes tables with more than 1MB of fragmented space, which keeps the operation fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Practical Checklist for Agency Multisite
&lt;/h2&gt;

&lt;p&gt;If you’re managing 20-100 client sites on multisite, here’s what actually moves the needle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Redis object caching&lt;/strong&gt; – this alone is probably the single biggest performance improvement. Not optional past 10 sites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit plugin activation scope&lt;/strong&gt; – network activate only what every site needs. Per-site activate everything else. This reduces memory usage and database queries on sites that don’t need specific plugins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean autoloaded options per site&lt;/strong&gt; – check each site’s options table for bloated autoload data. Transients, orphaned plugin settings, serialized blobs – they all add up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove unused sites&lt;/strong&gt; – archived and deactivated sites still have their tables in the database. If a client left 6 months ago, export and delete that site. Fewer tables = faster schema operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid switch_to_blog loops&lt;/strong&gt; – if you’ve got custom code or plugins that iterate over all sites, replace them with direct queries or batch operations via WP-CLI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use WP-CLI for maintenance&lt;/strong&gt; – &lt;code&gt;wp site list&lt;/code&gt; combined with &lt;code&gt;xargs&lt;/code&gt; lets you run operations across all sites without the overhead of switch_to_blog:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="n"&gt;wp&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="k"&gt;list&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;xargs&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;I&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="n"&gt;wp&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="n"&gt;transient&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;expired&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Monitor per-site performance independently&lt;/strong&gt; – a slow site in your network affects the shared database resources. Use query monitoring to identify which site is being the noisy neighbor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider the wp_is_large_network threshold&lt;/strong&gt; – at 10,000 users, WordPress defers user counts to cron. If your network admin is slow before that threshold, lower it:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'wp_is_large_network'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$is_large&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$count&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'users'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$component&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$is_large&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When to Abandon Multisite
&lt;/h2&gt;

&lt;p&gt;I know this isn’t what you want to hear in an article about scaling multisite, but it’s worth saying – sometimes the right move is to not use multisite at all.&lt;/p&gt;

&lt;p&gt;If your agency’s client sites share almost nothing – different themes, different plugins, different update schedules – you’re not really getting the benefits of multisite. You’re just getting the constraints. Separate WordPress installs with a management tool like MainWP or ManageWP give you centralized control without the shared database bottleneck.&lt;/p&gt;

&lt;p&gt;Multisite makes sense when sites share significant infrastructure – same theme, similar plugin sets, shared user base. Think universities with department sites, franchises with location pages, or media companies with multiple publications. If that’s not your use case, the &lt;a href="https://makewpfast.com/wordpress-database-optimization/" rel="noopener noreferrer"&gt;database optimization&lt;/a&gt; effort spent on multisite might be better spent on a simpler architecture.&lt;/p&gt;

&lt;p&gt;The honest take – multisite at 50+ sites is manageable with proper caching, selective plugin activation, and database monitoring. But it requires deliberate architecture decisions upfront. Bolting on performance fixes after you’ve already got 50 client sites sharing a single MySQL instance is… not fun. Ask me how I know.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>performance</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>I Tried to Run Every Plugin on WordPress.org. 1 in 16 Wouldn’t Even Start.</title>
      <dc:creator>MakeWPFast</dc:creator>
      <pubDate>Fri, 19 Jun 2026 08:15:12 +0000</pubDate>
      <link>https://dev.to/make-wp-fast/i-tried-to-run-every-plugin-on-wordpressorg-1-in-16-wouldnt-even-start-inc</link>
      <guid>https://dev.to/make-wp-fast/i-tried-to-run-every-plugin-on-wordpressorg-1-in-16-wouldnt-even-start-inc</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://makewpfast.com/wordpress-org-plugin-quality-benchmark/" rel="noopener noreferrer"&gt;https://makewpfast.com/wordpress-org-plugin-quality-benchmark/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To benchmark how much each WordPress plugin slows down a site, I first had to do something nobody seems to do at scale: install every plugin on WordPress.org, one at a time, in a clean isolated environment, and switch it on.&lt;/p&gt;

&lt;p&gt;That second step turned into an accidental audit of WordPress plugin quality. Most plugins behaved. But a stubborn minority couldn’t clear the lowest possible bar — turning on without taking the site down with them.&lt;/p&gt;

&lt;p&gt;Here’s the number that matters first, because I don’t want you to walk away thinking the plugin directory is a minefield: &lt;strong&gt;about 94% of the plugins I tested activated cleanly and ran in all three contexts I measure.&lt;/strong&gt; The WordPress plugin ecosystem is, broadly, fine.&lt;/p&gt;

&lt;p&gt;But I benchmarked &lt;strong&gt;54,649&lt;/strong&gt; plugins, and &lt;strong&gt;3,313 of them — roughly 1 in 16 — permanently failed to even run.&lt;/strong&gt; Not “ran slowly.” Not “had a bug somewhere deep in a settings page.” Failed to start. White screen, fatal error, or worse.&lt;/p&gt;

&lt;p&gt;This post is about that 6%: what breaks, why, and how to spot the pattern before one of them ends up on your site.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I tested
&lt;/h2&gt;

&lt;p&gt;Every plugin gets the same treatment, with no human in the loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spin up a clean, isolated WordPress container (current WordPress 6.9.x, a normal PHP version).&lt;/li&gt;
&lt;li&gt;Install the plugin straight from the WordPress.org repository.&lt;/li&gt;
&lt;li&gt;Activate it.&lt;/li&gt;
&lt;li&gt;Measure three contexts: &lt;strong&gt;activation&lt;/strong&gt;, the &lt;strong&gt;wp-admin&lt;/strong&gt; dashboard, and the &lt;strong&gt;homepage&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No other plugins. No theme tricks. No “it works on my machine.” If a plugin can’t survive a fresh, vanilla install, that’s not an edge case — that’s the plugin.&lt;/p&gt;

&lt;p&gt;When a plugin fails the same way repeatedly, I mark it as a permanent failure and record exactly &lt;em&gt;how&lt;/em&gt; it died. Those death certificates are what this post is built on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq764mljb2kl586d23daq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq764mljb2kl586d23daq.webp" alt="Bar chart: why roughly 3,300 WordPress.org plugins would not start, by failure category" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Failure categories among the ~3,313 WordPress.org plugins that would not start.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The five ways a plugin fails to start
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. It crashes the site on activation (1,172 plugins)
&lt;/h3&gt;

&lt;p&gt;This is the big one, and the worst one. You click “Activate” and the site falls over with a PHP fatal error. No graceful message, no “please configure me first” — just a &lt;a href="https://makewpfast.com/how-to-fix-the-wordpress-white-screen-of-death-wsod/" rel="noopener noreferrer"&gt;white screen of death&lt;/a&gt; or a &lt;a href="https://makewpfast.com/wordpress-500-internal-server-error-a-systematic-fix-guide/" rel="noopener noreferrer"&gt;500 error&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Breaking down &lt;em&gt;why&lt;/em&gt; they fataled tells you a lot about how the package was shipped:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What killed it&lt;/th&gt;
&lt;th&gt;Plugins&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Missing file (a require/include pointing at a file not in the zip)&lt;/td&gt;
&lt;td&gt;510&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Call to an undefined function or method&lt;/td&gt;
&lt;td&gt;440&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other fatal errors&lt;/td&gt;
&lt;td&gt;159&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uncaught exception&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Class not found&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;510 missing-file&lt;/strong&gt; crashes are the most damning. That’s not a subtle compatibility issue — it means the plugin author published a zip that doesn’t contain all of its own code. It never could have worked. Nobody activated it once before shipping.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;440 undefined-function&lt;/strong&gt; crashes are the classic PHP-version mismatch: the code assumes a function exists that your PHP doesn’t have (or the author leans on another plugin that isn’t there). It’s the same failure mode behind most &lt;a href="https://makewpfast.com/why-wordpress-sites-crash-after-updates-and-how-to-prevent-it/" rel="noopener noreferrer"&gt;crashes after an update&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. It dumps junk during activation (254 plugins)
&lt;/h3&gt;

&lt;p&gt;These don’t fatal — they’re messier. On activation they print raw text or PHP notices straight into the response. WordPress even flags it for you: &lt;em&gt;“The plugin generated unexpected output.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It looks harmless. It isn’t. Output during activation can corrupt HTTP headers, break redirects, and quietly sabotage anything that runs after it. More to the point, it’s a tell: if a plugin is leaking notices in production, the author shipped with debugging on and never looked at the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. It has secret dependencies (~200 plugins)
&lt;/h3&gt;

&lt;p&gt;This is the rudest category, because it’s so avoidable. The plugin needs another plugin to function — and instead of saying so, it just fatals or refuses to activate.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hidden requirement&lt;/th&gt;
&lt;th&gt;Plugins&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Requires WooCommerce&lt;/td&gt;
&lt;td&gt;146&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires Elementor&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires Contact Form 7&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires Genesis / bbPress / others&lt;/td&gt;
&lt;td&gt;~10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;WordPress has supported a proper &lt;code&gt;Requires Plugins&lt;/code&gt; header since version 6.5. A well-built add-on detects the missing dependency and shows a polite admin notice. These ones don’t bother — they assume you already have the parent plugin and detonate when you don’t. If you’ve ever fought a mysterious &lt;a href="https://makewpfast.com/wordpress-plugin-conflicts-how-to-diagnose-and-resolve-them/" rel="noopener noreferrer"&gt;plugin conflict&lt;/a&gt;, this is often the root.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. It’s abandoned (36 plugins)
&lt;/h3&gt;

&lt;p&gt;A small but telling group: &lt;strong&gt;31&lt;/strong&gt; have version requirements so out of step with current WordPress that they won’t load, and &lt;strong&gt;5&lt;/strong&gt; ship with invalid file encoding — a sign the code hasn’t been touched in years. These are the plugins still listed in the directory with a four-year-old “last updated” date, waiting for someone to install them and get burned.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. It’s actively dangerous (30 plugins)
&lt;/h3&gt;

&lt;p&gt;The rarest and scariest. &lt;strong&gt;28&lt;/strong&gt; plugins made WordPress completely unresponsive on activation — a hang or a runaway loop that takes the whole site with it. &lt;strong&gt;2&lt;/strong&gt; triggered WordPress’s own critical-error protection. Thirty plugins out of 54,000 is a tiny fraction, but if one of them is the one you install on a Friday afternoon, the statistics won’t save your weekend.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does &lt;em&gt;not&lt;/em&gt; mean
&lt;/h2&gt;

&lt;p&gt;I want to be precise here, because it would be easy to turn this into a scare piece, and that wouldn’t be honest.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~94% of plugins were completely fine.&lt;/strong&gt; The directory is not broken. Most authors do solid work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not every failure is the author’s fault.&lt;/strong&gt; About 80 of those 3,313 are artifacts of my automated harness — a container hitting a memory ceiling, a transient network blip, a benchmark that gave up too early. I exclude those from the “bad plugin” story.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I give borderline cases the benefit of the doubt.&lt;/strong&gt; While writing this, I found ~500 plugins that had activated fine and rendered a homepage, but tripped my admin-screen check — almost certainly setup wizards that redirect on activation, not real failures. I pulled them out of the failure set and re-queued them. The 3,313 that remain are the ones that genuinely wouldn’t run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the clearly-the-plugin’s-fault group is somewhere around 1,700–1,900. Call it 3% of everything I tested. Small. But “small” is cold comfort when it’s your client’s store that won’t load.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to vet a plugin before you install it
&lt;/h2&gt;

&lt;p&gt;You don’t need my infrastructure to avoid most of this. You need about ninety seconds and a little discipline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check the “Last updated” date.&lt;/strong&gt; Anything past a year is a yellow flag; past two years is a red one. This single check catches most of the abandoned category.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check active installs vs. reviews.&lt;/strong&gt; A plugin with 50 installs and no reviews has, by definition, not been activated on many real sites. You’d be the QA team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the most recent 1- and 2-star reviews.&lt;/strong&gt; Not the average — the recent angry ones. “White screen after activating” tells you everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look for stated requirements.&lt;/strong&gt; If it’s an add-on for WooCommerce or Elementor, the listing should say so loudly. Silence is a warning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test on staging first. Always.&lt;/strong&gt; Clone the site, activate there, click around the admin and the front end. Most managed hosts give you one-click staging. Never let a plugin’s first activation be on production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Activate one at a time.&lt;/strong&gt; If something breaks, you want to know exactly what did it. This is also the fastest way to &lt;a href="https://makewpfast.com/find-which-plugin-slowing-down-wordpress/" rel="noopener noreferrer"&gt;find the plugin slowing your site down&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this is exotic. It’s the difference between a five-minute install and a Saturday spent in &lt;a href="https://makewpfast.com/emergency-wordpress-recovery-what-to-do-when-your-site-goes-down/" rel="noopener noreferrer"&gt;recovery mode&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this data comes from
&lt;/h2&gt;

&lt;p&gt;These numbers aren’t a survey or a hunch. They come from the MakeWPFast benchmark dataset — every active plugin on WordPress.org, installed and measured in isolation, with its activation behavior and performance impact recorded. I built it to answer “how much does &lt;em&gt;this&lt;/em&gt; plugin actually slow a site down,” and the failure data fell out as a byproduct.&lt;/p&gt;

&lt;p&gt;If you want to query it yourself — speed scores, activation results, and benchmark data per plugin — it’s available through the &lt;a href="https://makewpfast.com/api/" rel="noopener noreferrer"&gt;MakeWPFast Benchmark API&lt;/a&gt;, and you can browse the &lt;a href="https://makewpfast.com/plugins/" rel="noopener noreferrer"&gt;full plugin index&lt;/a&gt; directly.&lt;/p&gt;

&lt;p&gt;The takeaway isn’t “be afraid of plugins.” It’s that a plugin being in the official directory means almost nothing about whether it works. Ninety seconds of checking, and a staging site, will keep you out of the 6%.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Do Too Many Plugins Slow Down WordPress? I Benchmarked 55,000 to Find Out.</title>
      <dc:creator>MakeWPFast</dc:creator>
      <pubDate>Fri, 19 Jun 2026 08:15:10 +0000</pubDate>
      <link>https://dev.to/make-wp-fast/do-too-many-plugins-slow-down-wordpress-i-benchmarked-55000-to-find-out-98g</link>
      <guid>https://dev.to/make-wp-fast/do-too-many-plugins-slow-down-wordpress-i-benchmarked-55000-to-find-out-98g</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://makewpfast.com/do-too-many-plugins-slow-wordpress/" rel="noopener noreferrer"&gt;https://makewpfast.com/do-too-many-plugins-slow-wordpress/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Do too many plugins slow down WordPress? Open any “speed up WordPress” article and you’ll hit the same answer in the first three bullets: &lt;strong&gt;you have too many plugins. Deactivate some. Fewer plugins, faster site.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s repeated so often it’s become a reflex. Slow site? Count your plugins.&lt;/p&gt;

&lt;p&gt;So I tested it. I benchmarked &lt;strong&gt;55,202&lt;/strong&gt; plugins* from the WordPress.org directory — each one installed and activated in a clean, isolated WordPress container, with the homepage’s load time measured before and after. If the “too many plugins” rule were true, you’d expect most plugins to add real, measurable weight.&lt;/p&gt;

&lt;p&gt;They don’t. &lt;strong&gt;The median plugin added 0 milliseconds to the homepage.&lt;/strong&gt; Not “a little.” Zero, within measurement noise. The number of plugins on your site is, for most plugins, almost irrelevant to how fast it loads.&lt;/p&gt;

&lt;p&gt;That needs caveats, and I’ll give them honestly below — because the myth survives for a real reason. But the headline holds: &lt;strong&gt;plugin count is the wrong thing to measure.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What “too many plugins” gets wrong
&lt;/h2&gt;

&lt;p&gt;The mental model behind the myth is that each plugin is a fixed tax. Twenty plugins, twenty taxes, slow site. Cut to ten, cut the tax in half.&lt;/p&gt;

&lt;p&gt;That’s not how WordPress works. A plugin that isn’t doing anything on a given request costs almost nothing on that request. A backup plugin does its work on a schedule, not when a visitor loads your homepage. An anti-spam plugin runs when a comment is submitted. A migration tool runs once, by you. None of them touch the front end of a normal page load, so none of them slow it down — no matter how many you stack up.&lt;/p&gt;

&lt;p&gt;The cost of a plugin isn’t “it exists.” The cost is “it runs code on this specific request.” Most don’t.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I measured (and what I didn’t)
&lt;/h2&gt;

&lt;p&gt;Every plugin got identical treatment, no human in the loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spin up a clean, isolated WordPress install (WordPress 6.9.x).&lt;/li&gt;
&lt;li&gt;Record the homepage’s time-to-first-byte (TTFB) with no plugin active.&lt;/li&gt;
&lt;li&gt;Install and activate the plugin.&lt;/li&gt;
&lt;li&gt;Record TTFB again. The difference is the plugin’s cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing to be clear about, because it’s the honest limit of this data: I measure a &lt;strong&gt;freshly activated, unconfigured&lt;/strong&gt; plugin on a &lt;strong&gt;default&lt;/strong&gt; site. This is the cost of &lt;em&gt;merely having the plugin active&lt;/em&gt; — not the cost once you’ve configured it and built your site around it. A page builder rendering a page you’ve packed with widgets will cost far more than its idle benchmark. WooCommerce on a store with 10,000 products is a different animal than WooCommerce sitting on a blank install.&lt;/p&gt;

&lt;p&gt;So read the result precisely: &lt;strong&gt;installing and activating more plugins does not, by itself, slow your front end.&lt;/strong&gt; What you &lt;em&gt;do&lt;/em&gt; with a few of them is a separate question.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result: the median plugin costs nothing
&lt;/h2&gt;

&lt;p&gt;Here’s the homepage TTFB delta across all 55,202 plugins:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Homepage TTFB added&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Median plugin&lt;/td&gt;
&lt;td&gt;0 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90th percentile&lt;/td&gt;
&lt;td&gt;10 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99th percentile&lt;/td&gt;
&lt;td&gt;25 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worst single plugin&lt;/td&gt;
&lt;td&gt;40,069 ms (a broken outlier)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And the distribution is the real story:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Homepage cost&lt;/th&gt;
&lt;th&gt;Plugins&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10 ms or less&lt;/td&gt;
&lt;td&gt;50,106&lt;/td&gt;
&lt;td&gt;90.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11–50 ms&lt;/td&gt;
&lt;td&gt;4,787&lt;/td&gt;
&lt;td&gt;8.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;51–100 ms&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;td&gt;0.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101–500 ms&lt;/td&gt;
&lt;td&gt;119&lt;/td&gt;
&lt;td&gt;0.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Over 500 ms&lt;/td&gt;
&lt;td&gt;77&lt;/td&gt;
&lt;td&gt;0.14%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1v6xock4sgqg8zf21bcg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1v6xock4sgqg8zf21bcg.webp" alt="Bar chart: 90.8% of WordPress plugins add 10ms or less to homepage load time; a tiny tail exceeds 100ms" width="799" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Homepage TTFB cost across 55,202 WordPress.org plugins. 90.8% add 10ms or less.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nine in ten plugins add 10 milliseconds or less.&lt;/strong&gt; Nearly 59% land within ±5 ms of zero — statistically indistinguishable from not having them at all. Memory tells the same story: the median plugin added &lt;strong&gt;0 KB&lt;/strong&gt; to the homepage’s peak memory, and only &lt;strong&gt;64&lt;/strong&gt; of the 55,202 added more than 5 MB.&lt;/p&gt;

&lt;p&gt;The plugins that genuinely hurt — the 196 that add more than 100 ms, the 77 that add more than half a second — are real, and they’re worth hunting down. But they’re &lt;strong&gt;0.36% of the directory.&lt;/strong&gt; You don’t find them by counting. You find them by measuring.&lt;/p&gt;

&lt;p&gt;It’s not just the homepage, either. In the admin dashboard the median plugin also added 0 ms (90th percentile 11 ms), and the same at activation. The “death by a thousand plugins” picture just isn’t in the data at the median.&lt;/p&gt;

&lt;h2&gt;
  
  
  So why does the myth refuse to die?
&lt;/h2&gt;

&lt;p&gt;Because it’s &lt;em&gt;almost&lt;/em&gt; pointing at something true. Three real things hide behind it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The heavy tail is real and it’s brutal.&lt;/strong&gt; That worst-case plugin added 40 seconds. A few hundred plugins hook into every single request — running queries, calling external APIs, loading large libraries on &lt;code&gt;init&lt;/code&gt;. If your slow site happens to be running two or three of those, deactivating plugins &lt;em&gt;will&lt;/em&gt; fix it. You just got lucky about which ones you removed. &lt;a href="https://makewpfast.com/find-which-plugin-slowing-down-wordpress/" rel="noopener noreferrer"&gt;Finding the actual culprit&lt;/a&gt; is faster than deactivating at random.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration and usage, not the plugin itself.&lt;/strong&gt; A page builder is cheap on a blank page and expensive on a page you’ve packed with widgets. That cost is real, but it’s &lt;em&gt;your&lt;/em&gt; page, not the plugin’s mere presence. Counting plugins won’t surface it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The genuinely broken ones.&lt;/strong&gt; In a &lt;a href="https://makewpfast.com/wordpress-org-plugin-quality-benchmark/" rel="noopener noreferrer"&gt;separate benchmark of plugin quality&lt;/a&gt;, I found roughly 1 in 16 plugins couldn’t even activate without erroring. Install one of those and “remove a plugin to fix the site” becomes literally true — but the lesson is “don’t install broken plugins,” not “install fewer.”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every one of those is a reason to be &lt;em&gt;selective&lt;/em&gt;, not a reason to be &lt;em&gt;minimal&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually slows your site
&lt;/h2&gt;

&lt;p&gt;If plugin count is the wrong metric, here’s where the time actually goes — roughly in order of how often it’s the real problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A handful of heavy plugins.&lt;/strong&gt; Not all of them. Two or three. &lt;a href="https://makewpfast.com/finding-what-makes-wordpress-slow/" rel="noopener noreferrer"&gt;Profile your site&lt;/a&gt; and you’ll usually find the weight concentrated in a few.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No page caching.&lt;/strong&gt; This dwarfs almost everything else. A cached page skips PHP and the database entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow hosting and no object cache.&lt;/strong&gt; &lt;a href="https://makewpfast.com/wordpress-object-cache-redis-vs-memcached/" rel="noopener noreferrer"&gt;Redis or Memcached&lt;/a&gt; for the database round-trips, and a host that isn’t overselling its CPUs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An &lt;a href="https://makewpfast.com/the-complete-wp-config-php-performance-tuning-guide/" rel="noopener noreferrer"&gt;under-tuned &lt;code&gt;wp-config.php&lt;/code&gt;&lt;/a&gt; and a bloated database&lt;/strong&gt; full of revisions, transients, and &lt;a href="https://makewpfast.com/wordpress-memory-limit-find-whats-eating-php-memory/" rel="noopener noreferrer"&gt;exhausted memory&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice what’s not on that list: “the number 14 being too high.”&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do instead of counting plugins
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stop deactivating at random.&lt;/strong&gt; It’s how the myth got started, and it occasionally works by luck. Measure first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find your heavy few.&lt;/strong&gt; Point a profiler at a slow page and it will show you which plugins actually run on that request and how long each one takes. &lt;a href="https://wpmultitool.com" rel="noopener noreferrer"&gt;WP Multitool’s &lt;em&gt;Find Slow Callbacks&lt;/em&gt;&lt;/a&gt; does exactly that — give it any URL and it times every hook and callback, then names the plugins or themes slowing it down. The answer is almost always a short list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judge a plugin before you install it,&lt;/strong&gt; not after your site is slow. Check the last-updated date, the active installs, and the recent reviews.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix the fundamentals first&lt;/strong&gt; — caching, hosting, object cache. They beat any amount of plugin-pruning.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The numbers behind this post come from the MakeWPFast benchmark dataset: every active plugin on WordPress.org, installed and measured in isolation. You can query a given plugin’s measured cost through the &lt;a href="https://makewpfast.com/api/" rel="noopener noreferrer"&gt;Benchmark API&lt;/a&gt;, or browse the &lt;a href="https://makewpfast.com/plugins/" rel="noopener noreferrer"&gt;full plugin index&lt;/a&gt;. And if you’d rather see those scores where you actually make the decision, &lt;a href="https://wpmultitool.com" rel="noopener noreferrer"&gt;WP Multitool&lt;/a&gt; drops a &lt;em&gt;Plugin Performance Score&lt;/em&gt; column straight onto your WordPress plugins page — each plugin’s benchmark score, memory use, and database queries, pulled from this same dataset — so you can spot the heavy ones without leaving wp-admin.&lt;/p&gt;

&lt;p&gt;The takeaway isn’t “install everything.” It’s that &lt;strong&gt;“too many plugins” is lazy diagnosis.&lt;/strong&gt; Ten well-built plugins will outrun three bloated ones every time. Stop counting. Start measuring.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;About the 55,202.&lt;/strong&gt; The WordPress.org directory holds more plugins than that. This figure counts the ones that installed, activated cleanly, and returned a valid homepage measurement — the only plugins whose front-end cost can honestly be measured. Several thousand more were tested but couldn’t be benchmarked at all, because they failed to even activate: broken packages, fatal errors, missing dependencies. Those are a separate story, which I dug into in &lt;a href="https://makewpfast.com/wordpress-org-plugin-quality-benchmark/" rel="noopener noreferrer"&gt;We Tried to Run Every Plugin on WordPress.org&lt;/a&gt;. Excluding them here is deliberate — you can’t measure the load time of a plugin that won’t load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; a Redditor challenged the claims in this post, so I installed 223 plugins on one 256 MB box to settle it. &lt;a href="https://makewpfast.com/how-many-plugins-can-wordpress-handle/" rel="noopener noreferrer"&gt;Here is what actually broke&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How Many Plugins Can WordPress Handle? I Installed 223 to Find Out</title>
      <dc:creator>MakeWPFast</dc:creator>
      <pubDate>Thu, 18 Jun 2026 08:15:13 +0000</pubDate>
      <link>https://dev.to/make-wp-fast/how-many-plugins-can-wordpress-handle-i-installed-223-to-find-out-495k</link>
      <guid>https://dev.to/make-wp-fast/how-many-plugins-can-wordpress-handle-i-installed-223-to-find-out-495k</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://makewpfast.com/how-many-plugins-can-wordpress-handle/" rel="noopener noreferrer"&gt;https://makewpfast.com/how-many-plugins-can-wordpress-handle/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After I published &lt;a href="https://makewpfast.com/do-too-many-plugins-slow-wordpress/" rel="noopener noreferrer"&gt;the post about whether too many plugins slow WordPress down&lt;/a&gt;, a Redditor called my bluff. The benchmark data says the median plugin adds 0ms to your homepage – fine. So install as many of those “fast” plugins as you can on one box and prove the count doesn’t matter.&lt;/p&gt;

&lt;p&gt;Ok. Challenge accepted. I took a fresh WordPress install on a deliberately miserable box – 256 MB PHP memory limit, 1 vCPU, no page caching at all – and started stacking the fastest, most popular plugins from &lt;a href="https://makewpfast.com/wordpress-org-plugin-quality-benchmark/" rel="noopener noreferrer"&gt;my benchmark database&lt;/a&gt; until something broke. I wanted a real number for how many plugins WordPress can actually handle before it falls over.&lt;/p&gt;

&lt;p&gt;I got to 223 active plugins. The homepage was still serving HTTP 200. And almost everything I believed going in turned out to be half wrong – including my first reading of how my own optimizer plugin behaved on the pile.&lt;/p&gt;

&lt;p&gt;This got long, so here’s the map: the climb to 223, the one plugin that took the whole site down, what an optimizer plugin can and can’t do here, the hunt for the worst offenders, and the fix that actually worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Fresh WordPress 7.0 in an isolated Docker container&lt;br&gt;
PHP &lt;code&gt;memory_limit = 256M&lt;/code&gt; – a hard wall, on purpose&lt;br&gt;
1 vCPU, no page cache, no object cache – every request fully dynamic&lt;br&gt;
Candidate pool: 5,202 plugins from the benchmark DB that are both fast (≤2ms activation TTFB delta) and popular (≥1,000 installs)&lt;/p&gt;

&lt;p&gt;The exclusions matter for honesty. I dropped all caching plugins (one LiteSpeed Cache would turn every measurement into a cache hit and the test would be meaningless), plus maintenance pages, login changers, redirects, firewalls, SSL forcers and 2FA – anything that would break the measuring itself rather than slow the site. I also added a 15 MB disk-size cutoff to honor “smallest”. Fair warning: the exclusion list wasn’t this clean on day one – it tightened during the run, every time something bit me. More on that below.&lt;/p&gt;

&lt;p&gt;Then: install in batches, activate, measure. Peak RAM per request, PHP files loaded, DB queries, TTFB. Repeat until the box gives up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The climb to 223
&lt;/h2&gt;

&lt;p&gt;Active plugins&lt;br&gt;
Peak RAM&lt;br&gt;
Files loaded&lt;br&gt;
DB queries&lt;br&gt;
TTFB&lt;/p&gt;

&lt;p&gt;0&lt;br&gt;
6 MB&lt;br&gt;
480&lt;br&gt;
25&lt;br&gt;
0.32 s&lt;/p&gt;

&lt;p&gt;30&lt;br&gt;
10 MB&lt;br&gt;
1,403&lt;br&gt;
107&lt;br&gt;
0.55 s&lt;/p&gt;

&lt;p&gt;60&lt;br&gt;
12 MB&lt;br&gt;
1,996&lt;br&gt;
173&lt;br&gt;
0.50 s&lt;/p&gt;

&lt;p&gt;100&lt;br&gt;
16 MB&lt;br&gt;
3,255&lt;br&gt;
260&lt;br&gt;
1.12 s&lt;/p&gt;

&lt;p&gt;150&lt;br&gt;
42 MB&lt;br&gt;
4,225&lt;br&gt;
420&lt;br&gt;
1.20 s&lt;/p&gt;

&lt;p&gt;191&lt;br&gt;
74 MB&lt;br&gt;
4,929&lt;br&gt;
498&lt;br&gt;
~2.7 s&lt;/p&gt;

&lt;p&gt;223&lt;br&gt;
102 MB&lt;br&gt;
5,726&lt;br&gt;
~586&lt;br&gt;
2-5 s&lt;/p&gt;

&lt;p&gt;The climb from 0 to 223 active plugins: queries and files rise near-linearly, RAM stays far from the 256 MB wall.&lt;br&gt;
Three things jumped out.&lt;/p&gt;

&lt;h3&gt;
  
  
  The count doesn’t eat your RAM
&lt;/h3&gt;

&lt;p&gt;The fear everyone repeats is “200 plugins will blow your memory”. On a box with OPcache, the front-end barely notices. Peak per-request memory went from 6 MB empty to 102 MB at 223 plugins – around 40% of the 256 MB wall, with the homepage still rendering fine.&lt;/p&gt;

&lt;p&gt;The reason is &lt;a href="https://www.php.net/opcache" rel="noopener noreferrer"&gt;OPcache&lt;/a&gt;. It holds the compiled plugin code in shared memory, outside the per-request budget. The plugin folder grew to 784 MB on disk and the rendered page didn’t care.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 256 MB wall is real – but it lives in wp-admin
&lt;/h3&gt;

&lt;p&gt;The memory limit did eventually bite. Just not where I expected. The homepage kept serving at 223 plugins. What died was the admin bootstrap: activating plugin #224 means loading all 223 existing plugins in admin context, which does far more work than a front-end render, and that blew straight through 256 MB. Later in the experiment wp-login.php itself stopped responding.&lt;/p&gt;

&lt;p&gt;So on a memory-starved box the ceiling never touched the visitors. It locked me out instead – no login, no plugin management, no admin at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  But the site absolutely got slower
&lt;/h3&gt;

&lt;p&gt;Here’s where the Redditor is only half right. Every active plugin registers hooks and runs queries on every page load, and that scales nearly linearly: queries went 25 → ~586 (23x), files loaded per request 480 → 5,726 (12x), and uncached TTFB from 0.3s to several seconds.&lt;/p&gt;

&lt;p&gt;The damage comes from the per-request work each active plugin adds, and it accumulates with every install. With page caching most of this disappears on a cache hit – which is exactly why I’d rather measure what each plugin costs than argue about counts. The articles claiming there’s &lt;a href="https://jetpack.com/resources/how-many-wordpress-plugins-are-too-many/" rel="noopener noreferrer"&gt;no fixed plugin limit&lt;/a&gt; are right about that part. They just never put a number on the accumulation, and the number is what makes it click.&lt;/p&gt;

&lt;h2&gt;
  
  
  One plugin took the whole site down
&lt;/h2&gt;

&lt;p&gt;223 well-behaved plugins crashed nothing. One plugin did, partway through the climb – it slipped in with an early batch, before my exclusion rules tightened:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PHP Fatal error: Uncaught Error: Call to undefined function&lt;br&gt;
  PersianWooCommerce\Services\wc_get_order_statuses() on init&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
persian-woocommerce (100k installs) calls a WooCommerce function on &lt;code&gt;init&lt;/code&gt;, but never declares &lt;code&gt;Requires Plugins: woocommerce&lt;/code&gt; in its header. WooCommerce wasn’t installed. WordPress happily let it activate anyway, and every request returned HTTP 500 – the site stayed down even after I deactivated everything else, until that one plugin was gone.&lt;/p&gt;

&lt;p&gt;All of that traces back to one missing line in a plugin header. Remember the header, because it comes back later in a much weirder way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can an optimizer plugin rescue it?
&lt;/h2&gt;

&lt;p&gt;The obvious follow-up. The pile is slow – can you install one more plugin that claws the speed back? I tested with WP Multitool 1.3.0. Full disclosure: &lt;a href="https://wpmultitool.com" rel="noopener noreferrer"&gt;that’s my own plugin&lt;/a&gt;. Which is exactly why I’m comfortable publishing what happened.&lt;/p&gt;

&lt;p&gt;First problem: wp-admin was unreachable. The 224-plugin admin bootstrap blows the 256 MB wall before the login page renders. So no optimizer’s admin UI can be opened on precisely the kind of site that needs rescuing – this applies to every optimization plugin, mine included. What saved the phase is that WP Multitool ships &lt;code&gt;wp multitool&lt;/code&gt; CLI commands: &lt;code&gt;frontend enable-all&lt;/code&gt;, &lt;code&gt;clean&lt;/code&gt;, &lt;code&gt;health&lt;/code&gt; all ran fine from the terminal, and a short &lt;code&gt;wp eval-file&lt;/code&gt; script covered the autoload optimizer (that part only exists as an admin AJAX action so far). Even wp-cli needed &lt;code&gt;php -d memory_limit=512M&lt;/code&gt; just to load the site. Without a CLI, this whole phase ends at a dead login screen – keep that in mind when you pick tooling for a site you might one day have to rescue.&lt;/p&gt;

&lt;p&gt;What got applied:&lt;/p&gt;

&lt;p&gt;Autoload optimizer: 31 options flipped off autoload, shrinking autoloaded options from 425 KB to 227 KB (-47%)&lt;br&gt;
All 13 frontend tweaks: defer scripts, strip emoji/dashicons/version strings/jquery-migrate/xmlrpc/oembed&lt;br&gt;
DB cleanup: 12 expired transients&lt;/p&gt;

&lt;p&gt;And the result, A/B measured by restoring database dumps between the two states:&lt;/p&gt;

&lt;p&gt;Metric&lt;br&gt;
223 plugins&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WP Multitool optimized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Median warm TTFB&lt;br&gt;
2.00 s&lt;br&gt;
2.08 s (within noise)&lt;/p&gt;

&lt;p&gt;DB queries / request&lt;br&gt;
562&lt;br&gt;
574&lt;/p&gt;

&lt;p&gt;PHP files / request&lt;br&gt;
5,577&lt;br&gt;
5,595&lt;/p&gt;

&lt;p&gt;Autoloaded options&lt;br&gt;
425 KB&lt;br&gt;
227 KB&lt;/p&gt;

&lt;p&gt;Honest methodology note, because I nearly published a wrong number here. My first quick A/B – two rounds under heavy host load – suggested the optimizer made the site 0.2-0.45s slower, and that almost went into this post. It smelled wrong though: the deterministic counters say it adds 12 queries and 18 files per request, about 2%, and 2% of work shouldn’t show up as 15% of TTFB. So I re-ran it properly: five alternating restore-measure rounds, 120 samples total. The per-round deltas flip sign (+0.19, -0.25, +0.40, -0.03, -0.43s) and the pooled difference is -1.5% median, Mann-Whitney z = 0.74 – statistically nothing. The “slower” reading was an artifact of my noisy 1-core box, and this is why you alternate A/B rounds instead of trusting two measurement windows.&lt;/p&gt;

&lt;p&gt;So the real result: no measurable TTFB change in either direction. The autoload cut is genuine (-47%) and would matter on a site where a bloated &lt;code&gt;alloptions&lt;/code&gt; row is the actual problem – but this site’s bottleneck is query volume and hook dispatch from 223 plugins, and no plugin can subtract queries that other plugins insist on running. The optimizer’s own footprint (+12 queries, +18 files) is real too, just small enough to disappear into the noise. You can’t plugin your way out of a plugin pile-up – but the right tooling is what let me drive, measure and eventually fix this site at all.&lt;/p&gt;

&lt;p&gt;A note on the numbers: each phase re-measured its own warm baseline, which settles a bit lower than the climb-day readings (562 queries vs the ~586 spike, 96 MB vs 102 MB). All comparisons are within the same phase, same load window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the plugins actually worth firing
&lt;/h2&gt;

&lt;p&gt;If adding software can’t fix it, the next move is subtraction: which of the 223 deserve to go?&lt;/p&gt;

&lt;p&gt;I ran a full leave-one-out sweep. For each of the 223 plugins: deactivate it, hit the homepage twice warm, record queries and files per request, reactivate, next. Query counts are deterministic per request, so a single reading ranks reliably even on a noisy single-core box – the measured noise floor was ±3 queries.&lt;/p&gt;

&lt;p&gt;The 10 most expensive of 223 “fast” plugins, by measured DB queries per page load. #2 is a performance plugin.&lt;br&gt;
Savor the second row. jetpack-boost – a performance plugin – is the #2 worst offender, spending 20 queries and 157 PHP files on every single request in order to make your site faster. Another image optimizer shows up at #12.&lt;/p&gt;

&lt;p&gt;The bigger finding is the shape of the data. The top 10 plugins (4.5% of the pile) account for 143 of the ~537 added queries – 27%. No plugin costs more than 23 queries. And the individual costs sum to ~92% of the total pile cost, so the damage is genuinely additive – no hidden interaction monster, and no single plugin to blame. The cost is spread across the whole pile.&lt;/p&gt;

&lt;p&gt;Still, 27% concentrated in 10 plugins is worth money. Deactivating just those 10, A/B measured back-to-back: queries 562 → 416, files down 654, peak RAM down 8.4 MB, median warm TTFB down 16%.&lt;/p&gt;

&lt;h3&gt;
  
  
  War stories from the sweep
&lt;/h3&gt;

&lt;p&gt;Running 223 deactivate-measure-reactivate cycles on a dying box produced two stories I didn’t expect.&lt;/p&gt;

&lt;p&gt;First, the MySQL container got OOM-killed mid-sweep and every wp-cli call started failing with “Error establishing a database connection”. The sweep script had to grow retries, a wait-for-DB loop and resume support. Fine, my fault for running this on a crowded VM.&lt;/p&gt;

&lt;p&gt;Second one’s better. WordPress 7’s plugin dependency check turned out to be a one-way door. FiboSearch and 7 other WooCommerce-dependent plugins had been running happily without WooCommerce for the entire experiment – they were activated before anything checked. But the moment my sweep deactivated one, WordPress refused to turn it back on: “requires woocommerce”. Running without its dependency: fine. Reactivating without it: forbidden. I had to write the &lt;code&gt;active_plugins&lt;/code&gt; option back directly.&lt;/p&gt;

&lt;p&gt;That’s the header paying off. persian-woocommerce skipped &lt;code&gt;Requires Plugins&lt;/code&gt;, activated freely and took the site down. FiboSearch declared it, degraded gracefully and survived the whole run – WordPress only got strict with it at reactivation time. Same missing-dependency situation, completely different blast radius.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replace the villains, keep the features
&lt;/h2&gt;

&lt;p&gt;Deleting 10 plugins is cheating if the site loses 10 features. So the last phase: replace every villain with a functional equivalent picked from the benchmark data, and keep every capability.&lt;/p&gt;

&lt;p&gt;Villain (queries/req)&lt;br&gt;
Replacement (queries/req)&lt;/p&gt;

&lt;p&gt;download-manager (23)&lt;br&gt;
simple-download-monitor (3)&lt;/p&gt;

&lt;p&gt;jetpack-boost (20)&lt;br&gt;
nothing – redundant&lt;/p&gt;

&lt;p&gt;image-optimization (20)&lt;br&gt;
shortpixel-image-optimiser – already installed&lt;/p&gt;

&lt;p&gt;pixelyoursite (18)&lt;br&gt;
koko-analytics (0)&lt;/p&gt;

&lt;p&gt;qi-addons-for-elementor (15)&lt;br&gt;
nothing – Elementor isn’t even installed&lt;/p&gt;

&lt;p&gt;iwp-client (11)&lt;br&gt;
nothing – management runs over SSH&lt;/p&gt;

&lt;p&gt;modula-best-grid-gallery (9)&lt;br&gt;
foogallery – already installed&lt;/p&gt;

&lt;p&gt;instagram-feed (9)&lt;br&gt;
insta-gallery – already installed&lt;/p&gt;

&lt;p&gt;broken-link-checker-seo (9)&lt;br&gt;
nothing – link checking belongs in an external crawler&lt;/p&gt;

&lt;p&gt;wp-reviews-plugin-for-google (9)&lt;br&gt;
widget-google-reviews – already installed&lt;/p&gt;

&lt;p&gt;Look at how often “already installed” appears. Four of the ten worst plugins duplicated functionality the site already had from a cheaper plugin: a second gallery, a second image optimizer, a second Instagram feed, a second Google-reviews widget. I only had to install two new plugins. Another one, qi-addons-for-elementor, was serving a page builder that didn’t exist on the site – 15 queries per request for nothing.&lt;/p&gt;

&lt;p&gt;I’d love to claim I planted those duplicates as a gotcha. I didn’t. They came in naturally with a “fast + popular” selection of 223 plugins, the same way they accumulate on real sites over years of “let’s try this one”.&lt;/p&gt;

&lt;p&gt;The result, A/B measured over two rounds in the same load windows:&lt;/p&gt;

&lt;p&gt;Same site, four states: the optimizer plugin made no measurable TTFB difference; the audit-and-replace pass cut queries 25% and TTFB 13%.&lt;/p&gt;

&lt;p&gt;Metric&lt;br&gt;
223 plugins&lt;br&gt;
215 after audit&lt;/p&gt;

&lt;p&gt;DB queries / request&lt;br&gt;
562&lt;br&gt;
422 (-25%)&lt;/p&gt;

&lt;p&gt;PHP files / request&lt;br&gt;
5,576&lt;br&gt;
4,952&lt;/p&gt;

&lt;p&gt;Peak RAM / request&lt;br&gt;
88 MB&lt;br&gt;
82 MB&lt;/p&gt;

&lt;p&gt;Median warm TTFB&lt;br&gt;
1.50 s&lt;br&gt;
1.31 s (-13%)&lt;/p&gt;

&lt;p&gt;Zero capability lost. A quarter of the database queries gone. Double-digit TTFB improvement on a fully uncached site – the -13% is a median across both rounds; individual windows ranged from -4% to -30% on this noisy box, so the query counter is the number I’d defend in court. And half of the fix was deleting things nobody knew were duplicated or dead.&lt;/p&gt;

&lt;p&gt;One last jetpack-boost anecdote, free of charge: while switching states for the A/B runs, bulk-reactivating the villains over wp-cli crashed twice at the site’s own 256 MB limit – both times inside jetpack-boost’s activation hook. The performance plugin literally cannot activate within the memory budget of the box it’s supposed to speed up. I had to raise the CLI limit to 512M for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’d actually do with this
&lt;/h2&gt;

&lt;p&gt;If you run a normal site with 30-80 plugins, the numbers scale down but the shape holds:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Page caching first.&lt;/strong&gt; Almost everything measured here is per-render cost. A cache hit skips it entirely. The uncached numbers are your worst case: logged-in users, carts, POST requests, cache misses.&lt;br&gt;
&lt;strong&gt;Audit your plugins individually.&lt;/strong&gt; 223 cheap plugins ran fine while one bad one killed the site, so the count itself tells you almost nothing. Per-plugin cost is measurable. Measure it.&lt;br&gt;
&lt;strong&gt;Hunt duplicates.&lt;/strong&gt; 4 of my 10 worst offenders duplicated an existing cheaper plugin. Check your galleries, image optimizers, analytics and social feeds.&lt;br&gt;
&lt;strong&gt;Be suspicious of plugins that promise speed.&lt;/strong&gt; The only plugin in my top 10 claiming to make the site faster cost 20 queries and 157 files per request – and my own optimizer, honestly measured, couldn’t move TTFB on this site because it fixes a different bottleneck. If a perf plugin can’t show you before/after numbers on &lt;em&gt;your&lt;/em&gt; site, assume nothing.&lt;br&gt;
&lt;strong&gt;Check for &lt;code&gt;Requires Plugins&lt;/code&gt; before you trust a dependency-shaped plugin.&lt;/strong&gt; Plugins that declare their dependencies fail politely. The ones that don’t can take your whole site down with them.&lt;br&gt;
&lt;strong&gt;Pick tools with a CLI.&lt;/strong&gt; When the pile finally kills wp-admin, the terminal is the only door left. Every fix in this post ran over wp-cli.&lt;/p&gt;

&lt;p&gt;The per-plugin costs I used to pick replacements come from benchmarking 54,000+ wordpress.org plugins across activation, homepage and admin contexts – that dataset is &lt;a href="https://makewpfast.com/api/" rel="noopener noreferrer"&gt;available as an API&lt;/a&gt; if you want to audit your own stack against it.&lt;/p&gt;

&lt;p&gt;The pile-up site is still running, by the way. 223 plugins active, homepage still HTTP 200. I’m weirdly proud of it.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>WordPress Cron Jobs: The Silent Performance Killer Nobody Talks About</title>
      <dc:creator>MakeWPFast</dc:creator>
      <pubDate>Tue, 16 Jun 2026 15:07:05 +0000</pubDate>
      <link>https://dev.to/make-wp-fast/wordpress-cron-jobs-the-silent-performance-killer-nobody-talks-about-13k</link>
      <guid>https://dev.to/make-wp-fast/wordpress-cron-jobs-the-silent-performance-killer-nobody-talks-about-13k</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://makewpfast.com/wordpress-cron-jobs-silent-performance-killer/" rel="noopener noreferrer"&gt;https://makewpfast.com/wordpress-cron-jobs-silent-performance-killer/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Your WordPress site randomly takes 3-4 seconds to load. You check again – 400ms. Refresh – back to 3 seconds. Then it’s fast again for the next 20 requests.&lt;/p&gt;

&lt;p&gt;You blame the hosting. You enable caching. You optimize images. Nothing fixes the random spikes. The problem isn’t your server, your theme, or your plugins. It’s wp-cron – and it’s been silently degrading your performance since the day you installed WordPress.&lt;/p&gt;

&lt;h2&gt;
  
  
  What wp-cron actually does on every page load
&lt;/h2&gt;

&lt;p&gt;WordPress doesn’t have access to your server’s task scheduler. So it built its own pseudo-cron system. Every single time someone loads a page on your site, WordPress runs this check: are there scheduled tasks waiting to execute?&lt;/p&gt;

&lt;p&gt;Here’s what happens under the hood. On every page load, WordPress calls &lt;code&gt;spawn_cron()&lt;/code&gt;. This function:&lt;/p&gt;

&lt;p&gt;Checks the &lt;code&gt;doing_cron&lt;/code&gt; transient to see if another cron process is already running&lt;br&gt;
Calls &lt;code&gt;wp_get_ready_cron_jobs()&lt;/code&gt; to find tasks with timestamps in the past&lt;br&gt;
If tasks are due, fires a non-blocking HTTP POST request back to your own site at &lt;code&gt;/wp-cron.php&lt;/code&gt;&lt;br&gt;
Sets a transient lock so other page loads don’t trigger duplicate runs&lt;/p&gt;

&lt;p&gt;Read that again. WordPress makes an HTTP request to itself on every page load where cron tasks are due. Your server is literally calling itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this causes random slowdowns
&lt;/h2&gt;

&lt;p&gt;The self-HTTP request is supposed to be non-blocking – WordPress sets a timeout of &lt;code&gt;0.01&lt;/code&gt; seconds and &lt;code&gt;'blocking' =&amp;gt; false&lt;/code&gt;. In theory, the page continues loading while cron runs in the background.&lt;/p&gt;

&lt;p&gt;In practice, several things go wrong:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The loopback request eats a PHP worker
&lt;/h3&gt;

&lt;p&gt;When WordPress fires that request to &lt;code&gt;wp-cron.php&lt;/code&gt;, your server needs to handle it like any other request. That means a PHP worker gets occupied running scheduled tasks – publishing scheduled posts, sending emails, running plugin maintenance, processing WooCommerce actions, clearing expired transients.&lt;/p&gt;

&lt;p&gt;On shared hosting with 2-4 PHP workers, one worker running cron means 25-50% of your capacity is gone. If cron takes 10 seconds to process a backlog of tasks, every visitor during that window competes for fewer workers.&lt;/p&gt;

&lt;p&gt;On a small server, a big enough backlog can tie up every PHP worker at once, and visitors start getting 502/504 gateway timeouts — the site is effectively offline until the queue drains. That’s usually temporary, but &lt;a href="https://fix-wp.com/" rel="noopener noreferrer"&gt;if your site is fully down&lt;/a&gt; and won’t recover on its own, emergency repair gets a crashed WordPress site back online in about an hour. Otherwise, the cron fix below stops scheduled tasks from ever monopolising your workers in the first place.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The loopback can fail or hang
&lt;/h3&gt;

&lt;p&gt;Some hosting configurations block loopback requests. Firewalls, misconfigured DNS, or SSL issues between your server and itself can make the “non-blocking” request actually block. I’ve seen sites where the cron loopback added 2-3 seconds to TTFB because the connection handshake timed out instead of completing instantly.&lt;/p&gt;

&lt;p&gt;You can test this. Go to Tools &amp;gt; Site Health in your WordPress admin. If you see “Your site could not complete a loopback request” – that’s the same mechanism wp-cron uses. And it’s broken.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Plugin cron sprawl
&lt;/h3&gt;

&lt;p&gt;Every plugin can register its own cron schedules. I’ve seen sites with 40+ scheduled cron events. Some plugins register tasks every 5 minutes. Others schedule one-off tasks and never clean them up, creating “orphaned” cron entries that pile up.&lt;/p&gt;

&lt;p&gt;WordPress checks ALL of these on every page load. The more cron events exist, the more work that check does.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to see what’s actually happening
&lt;/h2&gt;

&lt;p&gt;Before you fix anything, diagnose the problem. Here’s how to see your cron situation clearly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using WP-CLI
&lt;/h3&gt;

&lt;p&gt;If you have SSH access, WP-CLI gives you the best view:&lt;/p&gt;

&lt;h1&gt;
  
  
  List all scheduled cron events
&lt;/h1&gt;

&lt;p&gt;wp cron event list&lt;/p&gt;

&lt;h1&gt;
  
  
  Count total events
&lt;/h1&gt;

&lt;p&gt;wp cron event list --format=count&lt;/p&gt;

&lt;h1&gt;
  
  
  See all registered schedules
&lt;/h1&gt;

&lt;p&gt;wp cron schedule list&lt;/p&gt;

&lt;h1&gt;
  
  
  Test if cron can run
&lt;/h1&gt;

&lt;p&gt;wp cron test&lt;br&gt;
The &lt;code&gt;wp cron event list&lt;/code&gt; output shows you every scheduled task, when it’s due, and what schedule it’s on. Look for patterns – are there events scheduled every minute? Events that were supposed to run hours ago but haven’t? That’s your problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the cron option directly
&lt;/h3&gt;

&lt;p&gt;All cron data lives in one autoloaded option. You can see how bloated it is:&lt;/p&gt;

&lt;h1&gt;
  
  
  Check the size of your cron data
&lt;/h1&gt;

&lt;p&gt;wp option get cron --format=json | wc -c&lt;br&gt;
If that returns more than 10KB, you have cron bloat. I’ve seen sites where the &lt;code&gt;cron&lt;/code&gt; option was over 100KB – serialized data loaded into memory on every single page load, whether cron runs or not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the Query Monitor plugin
&lt;/h3&gt;

&lt;p&gt;Install &lt;a href="https://wordpress.org/plugins/query-monitor/" rel="noopener noreferrer"&gt;Query Monitor&lt;/a&gt; temporarily. It shows you exactly which cron events are registered and their schedules under the “Environment” panel. No SSH needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Find slow callbacks with WP Multitool
&lt;/h3&gt;

&lt;p&gt;If you’re already using &lt;a href="https://wpmultitool.com" rel="noopener noreferrer"&gt;WP Multitool&lt;/a&gt;, the Find Slow Callbacks module helps identify which hooks – including cron hooks – are consuming the most execution time. Combined with the Slow Query Analyzer, you can trace performance issues back to specific cron tasks hitting the database hard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: disable wp-cron, use real cron
&lt;/h2&gt;

&lt;p&gt;The solution is straightforward. Stop WordPress from checking cron on every page load, and use your server’s actual cron system to trigger it on a fixed schedule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Disable wp-cron in wp-config.php
&lt;/h3&gt;

&lt;p&gt;Add this line to your &lt;code&gt;wp-config.php&lt;/code&gt;, before the “That’s all, stop editing” comment:&lt;/p&gt;

&lt;p&gt;define( 'DISABLE_WP_CRON', true );&lt;br&gt;
This does exactly one thing: it tells WordPress to stop calling &lt;code&gt;spawn_cron()&lt;/code&gt; on page loads. Your scheduled tasks still exist – they just won’t be triggered by visitors anymore. For a deeper dive into wp-config.php settings that affect performance, check out &lt;a href="https://makewpfast.com/the-complete-wp-config-php-performance-tuning-guide/" rel="noopener noreferrer"&gt;the complete wp-config.php performance tuning guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Set up a real server cron job
&lt;/h3&gt;

&lt;p&gt;SSH into your server and edit your crontab:&lt;/p&gt;

&lt;p&gt;crontab -e&lt;br&gt;
Add one of these lines. Option A uses wget:&lt;/p&gt;

&lt;p&gt;*/15 * * * * wget -q -O /dev/null &lt;a href="https://yourdomain.com/wp-cron.php?doing_wp_cron" rel="noopener noreferrer"&gt;https://yourdomain.com/wp-cron.php?doing_wp_cron&lt;/a&gt; &amp;gt;/dev/null 2&amp;gt;&amp;amp;1&lt;br&gt;
Option B uses curl:&lt;/p&gt;

&lt;p&gt;*/15 * * * * curl -s &lt;a href="https://yourdomain.com/wp-cron.php?doing_wp_cron" rel="noopener noreferrer"&gt;https://yourdomain.com/wp-cron.php?doing_wp_cron&lt;/a&gt; &amp;gt;/dev/null 2&amp;gt;&amp;amp;1&lt;br&gt;
Option C calls PHP directly – no HTTP overhead at all:&lt;/p&gt;

&lt;p&gt;*/15 * * * * cd /path/to/wordpress &amp;amp;&amp;amp; php wp-cron.php &amp;gt;/dev/null 2&amp;gt;&amp;amp;1&lt;br&gt;
Option C is the best choice when possible. It skips the HTTP request entirely, which means no SSL handshake, no web server overhead, no loopback issues. The cron tasks run directly in PHP.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;*/15&lt;/code&gt; means “every 15 minutes.” That’s enough for most sites. If you publish scheduled posts and need them to go live on time, use &lt;code&gt;*/5&lt;/code&gt; for every 5 minutes. Going below 5 minutes is rarely necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Verify it’s working
&lt;/h3&gt;

&lt;p&gt;After setting up server cron, check that tasks are actually running:&lt;/p&gt;

&lt;h1&gt;
  
  
  Manually trigger and watch output
&lt;/h1&gt;

&lt;p&gt;wp cron event run --all --verbose&lt;/p&gt;

&lt;h1&gt;
  
  
  Check if your cron job is in the crontab
&lt;/h1&gt;

&lt;p&gt;crontab -l | grep wp-cron&lt;br&gt;
Then check your site’s cron event list again after 15-30 minutes. The “next run” times should be updating, proving that server cron is triggering them correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about managed hosting?
&lt;/h2&gt;

&lt;p&gt;Many managed WordPress hosts – WP Engine, Kinsta, Flywheel, Cloudways – already disable the default wp-cron and run it via server cron. Check your host’s documentation. Some handle this automatically. Others provide a UI to set cron intervals.&lt;/p&gt;

&lt;p&gt;If you’re on shared hosting without SSH access, some control panels (cPanel, Plesk) let you set up cron jobs through the web interface. Look for “Cron Jobs” or “Scheduled Tasks” in your hosting panel. The command you’d add is the wget or curl version from above.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ALTERNATE_WP_CRON option
&lt;/h2&gt;

&lt;p&gt;There’s a middle ground that some people miss. WordPress has another constant:&lt;/p&gt;

&lt;p&gt;define( 'ALTERNATE_WP_CRON', true );&lt;br&gt;
This changes the execution model. Instead of spawning a loopback request, it runs cron tasks on the &lt;code&gt;shutdown&lt;/code&gt; action – after the HTML has already been sent to the browser. The visitor doesn’t wait for cron, but it still runs on page loads.&lt;/p&gt;

&lt;p&gt;This is useful when loopback requests fail (the Site Health error I mentioned earlier) but you can’t set up server cron. It’s not as clean as real server cron – you still need traffic to trigger tasks – but it eliminates the self-HTTP problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning up cron bloat
&lt;/h2&gt;

&lt;p&gt;Switching to server cron fixes the triggering problem. But if you have dozens of orphaned cron events, they still add overhead when WordPress processes the cron option.&lt;/p&gt;

&lt;p&gt;Here’s how to clean up:&lt;/p&gt;

&lt;h1&gt;
  
  
  Find events from plugins you've already deactivated
&lt;/h1&gt;

&lt;p&gt;wp cron event list --fields=hook,next_run,recurrence&lt;/p&gt;

&lt;h1&gt;
  
  
  Delete a specific orphaned event
&lt;/h1&gt;

&lt;p&gt;wp cron event delete &amp;lt;hook_name&amp;gt;&lt;br&gt;
Look for hooks that reference plugins you no longer use. Common culprits include backup plugins that leave daily schedules behind, SEO plugins with hourly crawl checks, and analytics plugins pinging external APIs on a schedule.&lt;/p&gt;

&lt;p&gt;While you’re cleaning up scheduled tasks, it’s worth checking your database for other types of bloat too. Old &lt;a href="https://makewpfast.com/wordpress-database-bloat-cleaning-revisions-transients-and-spam/" rel="noopener noreferrer"&gt;revisions, expired transients, and spam comments&lt;/a&gt; cause similar silent performance degradation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The WooCommerce special case
&lt;/h2&gt;

&lt;p&gt;WooCommerce deserves its own mention. It relies heavily on wp-cron for processing scheduled sales, sending emails, cleaning up sessions, and running the Action Scheduler – which itself can queue hundreds of tasks.&lt;/p&gt;

&lt;p&gt;If you run WooCommerce and haven’t disabled wp-cron, you might see particularly bad spikes. The Action Scheduler can batch-process dozens of pending actions when cron triggers, occupying a PHP worker for 30+ seconds on busy stores.&lt;/p&gt;

&lt;p&gt;The fix is the same – server cron – but consider running it more frequently (&lt;code&gt;*/5&lt;/code&gt; instead of &lt;code&gt;*/15&lt;/code&gt;) for WooCommerce stores to keep the Action Scheduler queue from backing up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring after the fix
&lt;/h2&gt;

&lt;p&gt;Once you’ve made the switch, here’s what to watch for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consistent TTFB&lt;/strong&gt; – The random spikes should disappear. If you were seeing 3-second outliers mixed with 400ms responses, they should level out.&lt;br&gt;
&lt;strong&gt;Scheduled posts publishing on time&lt;/strong&gt; – The “Missed schedule” error on posts means cron isn’t running. If you see this, your server cron job isn’t configured correctly.&lt;br&gt;
&lt;strong&gt;Plugin features still working&lt;/strong&gt; – Some plugins depend on frequent cron runs. If something stops working after you make the switch, check what cron interval it expects and adjust your server cron frequency.&lt;/p&gt;

&lt;p&gt;If you’re still hunting down what’s making your WordPress site slow after fixing cron, the approach in &lt;a href="https://makewpfast.com/optimizing-wordpress-for-core-web-vitals-in-2026/" rel="noopener noreferrer"&gt;optimizing WordPress for Core Web Vitals&lt;/a&gt; covers the other common bottlenecks – LCP, CLS, and INP.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;WP-Cron is one of those things that works “well enough” on small sites with low traffic. But as your site grows, the cost of checking cron on every page load – and especially the self-HTTP loopback request – creates unpredictable performance that no amount of caching can fully fix.&lt;/p&gt;

&lt;p&gt;The fix takes 5 minutes: one line in wp-config.php and one line in crontab. It’s one of the highest-impact, lowest-effort WordPress performance optimizations you can make. And unlike most performance tweaks, you’ll notice the difference immediately in your TTFB consistency.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>WordPress Security Plugins and Performance: The Hidden Trade-off</title>
      <dc:creator>MakeWPFast</dc:creator>
      <pubDate>Tue, 26 May 2026 15:24:32 +0000</pubDate>
      <link>https://dev.to/make-wp-fast/wordpress-security-plugins-and-performance-the-hidden-trade-off-5965</link>
      <guid>https://dev.to/make-wp-fast/wordpress-security-plugins-and-performance-the-hidden-trade-off-5965</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://makewpfast.com/wordpress-security-plugins-performance-hidden-tradeoff/" rel="noopener noreferrer"&gt;https://makewpfast.com/wordpress-security-plugins-performance-hidden-tradeoff/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every WordPress security plugin promises to protect your site. What they don’t advertise is the performance cost.&lt;/p&gt;

&lt;p&gt;I’ve spent years optimizing WordPress sites, and one pattern keeps showing up – security plugins adding serious overhead to every single page load. Not just during scans. On every request.&lt;/p&gt;

&lt;p&gt;Here’s what’s actually happening under the hood, which plugins are the heaviest, and how to get proper security without tanking your site speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Security Plugins Hook Into Every Request
&lt;/h2&gt;

&lt;p&gt;Most people think their security plugin only does something during scheduled scans. That’s wrong.&lt;/p&gt;

&lt;p&gt;Plugins like Wordfence, Sucuri, and Solid Security (formerly iThemes Security) hook into the WordPress request lifecycle early – often at the &lt;code&gt;init&lt;/code&gt; or &lt;code&gt;plugins_loaded&lt;/code&gt; action. Some bypass WordPress entirely and load via an auto-prepend PHP file.&lt;/p&gt;

&lt;p&gt;On every single page load, here’s what a typical security plugin does:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firewall rule evaluation&lt;/strong&gt; – checks the incoming request against a ruleset (SQL injection patterns, XSS attempts, path traversal, etc.)&lt;br&gt;
&lt;strong&gt;IP reputation lookup&lt;/strong&gt; – compares the visitor’s IP against a blocklist, sometimes hitting an external API&lt;br&gt;
&lt;strong&gt;Login attempt tracking&lt;/strong&gt; – queries the database to check brute force thresholds&lt;br&gt;
&lt;strong&gt;File integrity monitoring&lt;/strong&gt; – some plugins hash core files on every request or on a frequent cron schedule&lt;br&gt;
&lt;strong&gt;Request logging&lt;/strong&gt; – writes every visit to a custom database table for the “live traffic” feature&lt;/p&gt;

&lt;p&gt;That’s a lot of PHP execution and database queries happening before your actual page content even starts rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Each Major Plugin Does Under the Hood
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Wordfence
&lt;/h3&gt;

&lt;p&gt;Wordfence runs its firewall at the PHP level using an auto-prepend file (&lt;code&gt;wordfence-waf.php&lt;/code&gt;). This means it executes before WordPress even loads. Smart from a security perspective – it can block attacks before they hit your application. But it also means PHP is doing real work on every request, even for static-ish pages that could otherwise be served from cache.&lt;/p&gt;

&lt;p&gt;The heaviest features:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extended Protection mode&lt;/strong&gt; – this is the biggest culprit users report. It runs deep packet inspection on every request. Disabling it is the single most common fix for Wordfence-related slowdowns.&lt;br&gt;
&lt;strong&gt;Live Traffic logging&lt;/strong&gt; – writes every request to the database. On a busy site, that’s thousands of INSERT queries per hour competing with your actual content queries.&lt;br&gt;
&lt;strong&gt;Malware scanner&lt;/strong&gt; – when it runs (scheduled or manual), it reads and hashes every PHP file on your installation. On sites with thousands of plugins and theme files, this can pin the CPU for minutes.&lt;br&gt;
&lt;strong&gt;Firewall rules&lt;/strong&gt; – Wordfence maintains a large ruleset that gets evaluated against each request’s headers, parameters, and body. More rules means more regex matching per request.&lt;/p&gt;

&lt;p&gt;Wordfence processes everything locally on your server. That’s the fundamental trade-off – your server’s CPU and memory are doing double duty as both a web server and a security appliance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sucuri
&lt;/h3&gt;

&lt;p&gt;Sucuri’s free WordPress plugin takes a different approach for scanning – it calls out to Sucuri’s SiteCheck service remotely, which means the malware scan itself doesn’t eat your server CPU.&lt;/p&gt;

&lt;p&gt;But the free plugin still runs heavy local operations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File integrity monitoring&lt;/strong&gt; – hashes every file in your WordPress installation to detect changes. On sites with large media libraries or lots of plugins, this creates significant disk I/O.&lt;br&gt;
&lt;strong&gt;Audit logging&lt;/strong&gt; – tracks file changes, login attempts, and plugin updates in the database.&lt;br&gt;
&lt;strong&gt;Hardening checks&lt;/strong&gt; – evaluates security posture on admin pages.&lt;/p&gt;

&lt;p&gt;Sucuri’s paid cloud WAF is a different story – it sits in front of your server as a reverse proxy, filtering traffic at the edge. This actually reduces your server load because malicious requests never reach PHP. But we’re talking about a $200+/year service, not the free plugin most people install.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solid Security (iThemes Security)
&lt;/h3&gt;

&lt;p&gt;Solid Security hooks into WordPress at the application level. Independent testing shows it adds execution time comparable to Wordfence, which is notable because Wordfence has significantly more features.&lt;/p&gt;

&lt;p&gt;The overhead comes from:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Brute force protection&lt;/strong&gt; – database queries on every login page load to check attempt counts and lockout status.&lt;br&gt;
&lt;strong&gt;File change detection&lt;/strong&gt; – scheduled scans that read and hash files.&lt;br&gt;
&lt;strong&gt;404 detection&lt;/strong&gt; – logs every 404 error to identify scanning bots. On sites getting hit by automated scanners, this means constant database writes.&lt;br&gt;
&lt;strong&gt;Database logging&lt;/strong&gt; – maintains its own log tables that grow over time, adding to query overhead.&lt;br&gt;
&lt;strong&gt;Backend asset loading&lt;/strong&gt; – loads almost half a megabyte of JavaScript on admin pages.&lt;/p&gt;

&lt;p&gt;The IP ban list is another hidden cost. As it grows, every request has to check against it. On sites that have been running Solid Security for years, that list can get massive.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Numbers in Context
&lt;/h3&gt;

&lt;p&gt;Independent benchmarks from &lt;a href="https://accelerawp.com/best-free-wordpress-security-plugins-performance-comparison/" rel="noopener noreferrer"&gt;Accelera WP&lt;/a&gt; tested security plugins with firewalls and brute force protection enabled on default settings. The spread is significant:&lt;/p&gt;

&lt;p&gt;The lightest plugins (SecuPress, Security Optimizer) added roughly 12-25ms of frontend execution time&lt;br&gt;
Wordfence and Solid Security added around 55-60ms&lt;br&gt;
Shield Security added over 130ms&lt;/p&gt;

&lt;p&gt;Those are per-request numbers on a test environment. On a shared hosting server under real load with competing sites, those numbers get worse. And they compound – if you’re also running a page builder, WooCommerce, and an SEO plugin, you’re stacking overhead on top of overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Some Features Are Worse Than Others
&lt;/h2&gt;

&lt;p&gt;Not all security features cost the same. Here’s roughly how they rank from heaviest to lightest:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heavy:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Live traffic logging (constant DB writes)&lt;br&gt;
Full filesystem malware scans (CPU + disk I/O spikes)&lt;br&gt;
Extended/deep firewall modes (complex regex on every request)&lt;br&gt;
File integrity hashing on every request&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Medium:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Brute force tracking (DB reads/writes on login)&lt;br&gt;
IP blocklist checking (grows over time)&lt;br&gt;
404 monitoring and logging&lt;br&gt;
Comment spam protection&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Light:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Security headers (one-time HTTP header additions)&lt;br&gt;
Login URL changes (simple redirect)&lt;br&gt;
XML-RPC blocking (early request termination)&lt;br&gt;
File permission checks (admin-only, not per-request)&lt;/p&gt;

&lt;p&gt;The problem is that most plugins enable the heavy features by default. And most users never touch the settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem: PHP-Level Firewalls
&lt;/h2&gt;

&lt;p&gt;Here’s the fundamental issue that nobody talks about enough.&lt;/p&gt;

&lt;p&gt;A PHP-based firewall means your server has to boot PHP, load WordPress (or at least the WAF prepend file), parse the request, run it through firewall rules, and then either block it or continue to your actual page.&lt;/p&gt;

&lt;p&gt;For legitimate visitors, that’s wasted overhead. They were always going to get through – but your server still had to run all those checks.&lt;/p&gt;

&lt;p&gt;For attackers, it’s even worse. Your server is still spending CPU cycles processing their requests, even if it eventually blocks them. During a brute force attack or a bot flood, your PHP workers are busy evaluating firewall rules instead of serving pages to real visitors.&lt;/p&gt;

&lt;p&gt;This is backwards. You’re using your web server as a firewall, when there are purpose-built tools that handle this at the network level.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Get Security Without Killing Performance
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option 1: Cloudflare WAF (Best for Most Sites)
&lt;/h3&gt;

&lt;p&gt;Cloudflare’s WAF operates at the network edge – their servers evaluate requests before they ever reach yours. Malicious traffic gets blocked at the nearest Cloudflare data center, thousands of miles from your origin server.&lt;/p&gt;

&lt;p&gt;What this means in practice:&lt;/p&gt;

&lt;p&gt;Bot traffic never hits your PHP workers&lt;br&gt;
DDoS attacks get absorbed by Cloudflare’s network, not yours&lt;br&gt;
Your server only processes legitimate requests&lt;br&gt;
You get a CDN and performance optimization on top of the security&lt;/p&gt;

&lt;p&gt;The free Cloudflare plan includes basic WAF rules. The Pro plan ($20/month) gives you managed rulesets that cover OWASP Top 10. That’s less than most premium security plugins, and it actually makes your site faster instead of slower.&lt;/p&gt;

&lt;p&gt;You still want to pair this with basic WordPress hardening:&lt;/p&gt;

&lt;p&gt;Strong passwords and two-factor authentication (use a lightweight 2FA plugin)&lt;br&gt;
&lt;a href="https://makewpfast.com/xml-rpc-in-wordpress-the-security-and-performance-risk-you-should-disable/" rel="noopener noreferrer"&gt;Disable XML-RPC&lt;/a&gt; if you’re not using it&lt;br&gt;
Keep WordPress, themes, and plugins updated&lt;br&gt;
Use proper file permissions&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Server-Level Firewall
&lt;/h3&gt;

&lt;p&gt;If you have server access (VPS or dedicated), you can run firewall rules at the OS level with tools like fail2ban, ModSecurity, or iptables/nftables.&lt;/p&gt;

&lt;p&gt;fail2ban watches your logs and blocks IPs after failed login attempts – at the network level, before PHP even gets involved. ModSecurity runs as an Apache or Nginx module, evaluating requests before they hit PHP.&lt;/p&gt;

&lt;p&gt;This is how enterprise sites handle security. The firewall runs where it belongs – at the network or server level – not inside the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: Minimal Plugin Setup
&lt;/h3&gt;

&lt;p&gt;If you must use a plugin (maybe your client insists, or you need the audit logging for compliance), here’s how to minimize the damage:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disable live traffic logging.&lt;/strong&gt; You don’t need it. Use your server logs or Cloudflare analytics instead.&lt;br&gt;
&lt;strong&gt;Schedule scans for off-peak hours.&lt;/strong&gt; Run malware scans at 3 AM, not during business hours.&lt;br&gt;
&lt;strong&gt;Reduce scan frequency.&lt;/strong&gt; Weekly scans are fine for most sites. Daily is overkill.&lt;br&gt;
&lt;strong&gt;Disable file integrity checking on every request.&lt;/strong&gt; Scheduled checks are enough.&lt;br&gt;
&lt;strong&gt;Keep your IP blocklist clean.&lt;/strong&gt; Purge old entries regularly.&lt;br&gt;
&lt;strong&gt;Use a lightweight plugin.&lt;/strong&gt; SecuPress and All-In-One Security tested significantly lighter than Wordfence and Solid Security in independent benchmarks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 4: The Hybrid Approach (What I Actually Recommend)
&lt;/h3&gt;

&lt;p&gt;Here’s what I run on most sites I manage:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare&lt;/strong&gt; in front (free or Pro plan) – handles WAF, bot protection, DDoS mitigation&lt;br&gt;
&lt;strong&gt;No security plugin&lt;/strong&gt; – seriously. Cloudflare handles the heavy lifting.&lt;br&gt;
&lt;strong&gt;fail2ban&lt;/strong&gt; on the server for SSH and wp-login brute force protection&lt;br&gt;
&lt;strong&gt;Two-factor authentication&lt;/strong&gt; via a lightweight plugin (not a full security suite)&lt;br&gt;
&lt;strong&gt;Automatic updates&lt;/strong&gt; for minor WordPress releases and plugin security patches&lt;br&gt;
&lt;strong&gt;Regular backups&lt;/strong&gt; that are tested (security is also about recovery)&lt;/p&gt;

&lt;p&gt;This setup adds zero per-request overhead from security operations. All the heavy checking happens at the edge or at the OS level, not in PHP.&lt;/p&gt;

&lt;h2&gt;
  
  
  When You Actually Need a Security Plugin
&lt;/h2&gt;

&lt;p&gt;I’m not saying security plugins are always wrong. There are cases where they make sense:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared hosting where you can’t install server-level tools&lt;/strong&gt; – a plugin is your only option&lt;br&gt;
&lt;strong&gt;Compliance requirements&lt;/strong&gt; that demand file integrity monitoring with audit trails&lt;br&gt;
&lt;strong&gt;Multi-tenant environments&lt;/strong&gt; where you need per-site security policies&lt;br&gt;
&lt;strong&gt;Client sites where the client needs a dashboard&lt;/strong&gt; to see security status&lt;/p&gt;

&lt;p&gt;In those cases, pick the lightest plugin that meets your requirements, disable every feature you don’t need, and pair it with Cloudflare to reduce the load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check Your Own Site
&lt;/h2&gt;

&lt;p&gt;Want to know if your security plugin is slowing you down? Here’s a quick test:&lt;/p&gt;

&lt;p&gt;Measure your TTFB (Time to First Byte) with the security plugin active&lt;br&gt;
Deactivate the security plugin temporarily&lt;br&gt;
Measure TTFB again&lt;br&gt;
Compare the difference&lt;/p&gt;

&lt;p&gt;If you’re seeing a meaningful increase in TTFB with the plugin active, that’s your security tax on every single request.&lt;/p&gt;

&lt;p&gt;You might also want to check your &lt;a href="https://makewpfast.com/optimizing-wordpress-for-core-web-vitals-in-2026/" rel="noopener noreferrer"&gt;Core Web Vitals&lt;/a&gt; – security plugins can affect LCP and INP too, especially ones that load JavaScript on the frontend.&lt;/p&gt;

&lt;p&gt;While you’re at it, check if your security plugin is &lt;a href="https://makewpfast.com/wordpress-plugin-conflicts-how-to-diagnose-and-resolve-them/" rel="noopener noreferrer"&gt;conflicting with other plugins&lt;/a&gt;. Security plugins are notorious for breaking caching plugins, CDN integrations, and REST API functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;Security plugins run PHP code on every request. That costs time and server resources. Cloud-based WAFs like Cloudflare do the heavy lifting at the edge, where it belongs.&lt;/p&gt;

&lt;p&gt;The best security setup is often the one that doesn’t run inside WordPress at all.&lt;/p&gt;

&lt;p&gt;If your site feels sluggish and you can’t figure out why, your security plugin might be the culprit nobody suspected. Especially if you’re on shared hosting where resources are tight. Before you start &lt;a href="https://makewpfast.com/wordpress-database-bloat-cleaning-revisions-transients-and-spam/" rel="noopener noreferrer"&gt;debugging your database bloat&lt;/a&gt; or &lt;a href="https://makewpfast.com/how-to-properly-defer-javascript-in-wordpress/" rel="noopener noreferrer"&gt;deferring JavaScript&lt;/a&gt;, check that security plugin first. Sometimes the thing protecting your site is also the thing holding it back.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>security</category>
      <category>wordpress</category>
    </item>
  </channel>
</rss>
