Evaluat is in private access. Demos open through July. Book a slot

Blog Ecommerce Performance

WooCommerce performance testing: find your store's limit under load

WooCommerce runs your store, which means the slow paths are yours to fix. Its cached catalog flies, but cart, checkout, and my-account run PHP and MySQL on every request, and on budget hosting a thin worker pool caps how many shoppers that path can hold. Here is why it slows under load, and how to test a staging copy.

Written by: Ahmad Farzan ·

On a WooCommerce store, the cached catalog is served straight from cache, but cart and checkout run the full uncacheable path on every request: WordPress boot, autoloaded options, plugin hooks and queries, a PHP worker, and MySQL.

Doesn’t WooCommerce scale? It runs on WordPress.

Yes, with the right hosting and caching, and the cached catalog scales cheaply. The trap is that the parts shoppers spend money on, the cart, the checkout, and the my-account pages, are non-cacheable by design. They run PHP and MySQL on every request while the homepage is served from a stored copy.

WooCommerce powers more stores than any other ecommerce platform. It rides on WordPress, which runs 41.5% of all websites, and it accounts for 48.6% of every ecommerce system W3Techs tracks, with more than 4.2 million live stores as of June 2026. Most of those are small and mid-sized merchants on shared or managed WordPress hosting, which is exactly the audience this guide is for. Shopify leads among the largest, highest-traffic sites; WooCommerce owns the long tail.

And it scales. WooCommerce’s own scaling guidance is blunt about it: “The sky is the limit. We’ve seen instances of shops with 100,000+ products listed, handling thousands of transactions per minute.” The four things that decide whether yours does are traffic, WooCommerce’s own code, the rest of your plugin and theme code, and your server hardware. Every one of those is a lever you control. WooCommerce is slow by default and fast when tuned, which is the whole opportunity: the store you assembled is yours to fix.

So where does it slow down? On the paths a cache cannot help. WooCommerce marks the cart, checkout, and my-account pages as non-cacheable by default, and supported caching plugins honor that exclusion, because those pages show one shopper their own cart and cannot be shared (WooCommerce docs). A printed menu board works for the whole room, but every order still goes to a kitchen that cooks it from scratch. Your catalog is the menu board. Checkout is the kitchen, and the kitchen has a fixed number of cooks. This is the same pattern that takes down Magento checkout under load, scaled down to a WordPress store, and seeing whether yours holds takes a real-browser performance test, not a check of the homepage.

What actually runs on every WooCommerce request?

On the uncacheable paths, every request boots WordPress, reads the full set of autoloaded options, runs each active plugin’s hooks and queries, and builds the page in PHP. On budget hosting a handful of workers handle all of it, often with no object cache, so the database takes every uncached query directly. Here is what stacks up.

The cart-fragments AJAX call

WooCommerce refreshes the mini-cart with a script that, in its own words, “uses the WordPress admin-ajax API to update the cart without refreshing the page.” That call is uncached and handled by PHP. It used to be the textbook WooCommerce performance drag because, before WooCommerce 7.8, “the cart fragments script was enqueued on every page load … even if the mini-cart widget wasn’t present.” Since version 7.8 in June 2023 it loads by default only where the cart widget is actually rendered. The lesson is not that it is solved everywhere, because plenty of themes still render that widget on every page, and where the call fires it is origin work that multiplies with concurrency.

Autoloaded options in wp_options

WordPress keeps site settings in a table called wp_options, and every option flagged to autoload is read on each page request. Plugins add to it, and expired transients, temporary cached values, often linger there long after they are useful. WordPress is explicit about the cost: without a persistent object cache, “your web server must read those options from the database to handle every page view.” On a busy uncacheable path, a bloated options table is a tax paid on every single request.

Plugin sprawl

This is not about any one plugin. Each active plugin can add database queries, hook callbacks, and front-end scripts, and a store assembled from a dozen or more pays that cost on every request. WooCommerce names the rest of your plugin and theme code as one of the four things that decide whether you scale, and it is the one that grows quietly as a store ages. The fix is to measure which plugins cost the most, not to guess.

A small worker pool and no object cache

The ceiling is set by how many requests your server can run at once. PHP processes requests through a pool of workers, and budget shared or managed hosting often gives a site only a handful. When every worker is busy building an uncacheable checkout page, the next request waits, and past a point the server returns 502 or 503 errors. A persistent object cache (Redis or Memcached) would keep repeated queries in memory, but WordPress is clear that this depends on the host: “For your site to use persistent object caching, your hosting provider must offer you a particular type of server, a cache server.” Without one, every uncached query falls straight through to MySQL.

The front end shoppers actually feel

All of that is the server. The shopper also waits on the theme and plugin JavaScript that runs in their browser, plus any tracking tags, before the page is usable. This is where WooCommerce’s real-world numbers show the strain. Only 45% of WordPress sites pass all three Core Web Vitals on mobile, and the weakest of the three is loading: just 53% have a good Largest Contentful Paint, per the 2025 Web Almanac. The median page across all websites now ships 2,164 KB, including 646 KB of JavaScript, and a commerce store with a page builder and a dozen plugins tends to sit heavier. Those Vitals are measured in a browser, which is the first hint about why a server-only load test misses them. For how they move at concurrency, see Core Web Vitals at load.

Why does it only fall over under load?

Because the cheap path and the expensive path scale in opposite directions. The cached catalog barely notices a crowd. The uncacheable cart-and-checkout path lands on a fixed pool of PHP workers, and when more shoppers transact at once than there are workers to serve them, requests queue, response times climb, and the slowest ones time out as 502s and 503s.

A normal day hides this. Most visitors browse cached pages, a few buy, and the worker pool is never full. A sale changes both numbers at once: more visitors arrive, and a larger share of them head for the cart and checkout, the one place the cache cannot help. The catalog still flies while the kitchen falls behind. Third-party work makes it worse, because a checkout often calls out to a payment gateway or a shipping or tax service, and a worker waiting on a slow external response is a worker serving no one else. Your sale peak is their peak too. This is the Magento checkout pattern on a smaller stack: the platform differs, the bottleneck shape does not.

A diagram of WooCommerce under load. The cached catalog is a wide green flow that scales freely, while the uncacheable cart, checkout, and admin-ajax requests funnel into a small fixed pool of PHP workers that fills up and overflows into queued requests and 502 errors.

Why did your load test say everything was fine?

Because it probably tested the cache. Pointing virtual users at the homepage measures how fast a stored copy is served, a number no WordPress site struggles with. The uncached checkout, the admin-ajax calls, and the theme and plugin JavaScript only show up when the test logs in, adds to cart, and renders the page in a real browser.

A virtual user is one simulated shopper the test drives. For the test to exercise the path that breaks, each one needs its own session and cart, and the test has to run the journey, not hammer a single URL. The choice of tool matters here too. Protocol tools like k6 and JMeter are genuinely the right instrument for API and protocol concurrency, where there is no browser to run and a real one would be wasted overhead, and k6 even ships a browser module. But in HTTP mode they send requests and read the response bytes without executing the page’s JavaScript or rendering it, so the theme and plugin cost, the Core Web Vitals, and the admin-ajax behavior a browser would trigger are outside what they measure. That is the gap between what the server returns and what users see, not what scripts pretend.

What an HTTP test sees versus what a real browser sees

The two answer different questions. An HTTP test answers “did the server respond,” which on a cached WooCommerce catalog is almost always yes. A real-browser test answers “did the page stay usable for the shopper at peak,” which is the question your revenue turns on. The table maps what each one records during one request.

During one WooCommerce requestHTTP load test (HTTP mode)Real-browser test
Server returns the cached catalog HTMLMeasured, and fastMeasured, and fast
Uncached cart / checkout PHP pathHit, if scriptedHit, the way a shopper hits it
Theme and plugin JavaScript parse and executeNot runMeasured
admin-ajax cart-fragment behaviorNot triggered as a browser doesTriggered and measured
Largest Contentful Paint and INPNot producedCaptured per session
Evidence of why one session stalledNoneSession video, network log, console log

To see the layer that breaks, the test has to run the page the way a shopper’s device runs it. That is real-browser load testing: every virtual user is a real browser.

How to load test your WooCommerce store (on staging)

Because WooCommerce is self-hosted, you can test the whole journey, including checkout, which is the opposite of a hosted platform like Shopify, where the checkout is sandboxed and off-limits. Do it against a staging copy that mirrors production, with payments in sandbox mode. The goal is one number: the concurrency at which checkout stops meeting your targets.

  1. Test a staging copy, and check your host’s terms first. Only load test infrastructure you own or have permission to test, and use a staging or production-replica environment, not the live store. Many managed hosts restrict it. Kinsta, for example, states plainly that “load testing is prohibited on the MyKinsta platform” and points to its terms. A test against a shared box also measures the neighbours you share it with, so a copy on infrastructure that matches production is the honest target.
  2. Script the full journey, not a URL. Product page, add to cart (which fires the fragments call), cart, checkout, with the payment gateway in sandbox. The journey is what shoppers do, and the journey is what breaks.
  3. Give every virtual user its own data. A unique session, cart, and account per user keeps caching and database contention close to a real crowd. Hundreds of users sharing one cart collapse onto a single row and flatter the result.
  4. Find the worker-pool ceiling on purpose. Ramp virtual users past your expected peak until error rate and p95 response time climb together. That crossover is your concurrency ceiling, and the method is the same as stress testing a website. A flash sale or product drop, where the load lands in minutes, is the sharper case covered in spike testing.
  5. Read the results per step, in a real browser. A p95 spike on checkout points at PHP and MySQL; Largest Contentful Paint and INP drifting upward point at theme and plugin JavaScript. Modern stores keep order data in dedicated tables (High-Performance Order Storage, the default since WooCommerce 8.2 in October 2023), which eases order-write contention, but cart and checkout still run uncached PHP on every request, so the path that breaks first is unchanged.
  6. Re-test after plugin and theme changes. Every added plugin, tag, or theme update moves the ceiling. The margin is worth real money: Google and Deloitte’s 2020 study Milliseconds Make Millions found a 0.1 second mobile speedup lifted retail conversions by 8.4%.

How Evaluat tests a WooCommerce store under load

Evaluat is a real-browser performance testing platform: every virtual user is a real, isolated browser that walks your store the way a shopper would, capturing Core Web Vitals under load along with session video, network logs, and console logs for each user. You build the journey once in a visual scenario editor, with no scripting.

Datasets feed each virtual user its own account, cart, and search terms; popup handlers dismiss the cookie and consent banners every storefront carries; and tests run from the region closest to your customers. When checkout gives way at peak, the report shows where. The per-URL view separates the catalog pages that held from the checkout step that did not, and the network log of any session shows which call stalled, a fragment refresh, a plugin’s admin-ajax handler, a payment redirect. Aggregate percentiles tell you something broke; they do not tell you who or why. A failure at peak isn’t a percentile. It’s a session. Open the session. Watch the moment it broke.

Two honest boundaries. Evaluat watches your store from the outside: it shows which request was slow and what the shopper experienced, but finding the slow query inside MySQL or the bloated autoload table still takes a server-side profiler like Query Monitor. And for raw protocol concurrency, thousands of API requests per second with no rendering, an HTTP tool like k6 or JMeter is the lighter, cheaper instrument. The real-browser model earns its cost on the JavaScript-heavy, uncacheable path this guide is about.

Test the store you assembled, not the platform underneath

The asymmetry is the lesson. WooCommerce gives you a catalog that caches cheaply and a checkout that pays full price for every click, and because the whole stack is yours, the ceiling is yours to find and raise. Stop reading “WooCommerce scales” as proof that your store will. Script the journey that takes the money, run it in a real browser on a staging copy at the load you expect, and find the concurrency where it stops holding, before a sale finds it for you.

Test in real browsers. Debug in real sessions. Book a demo.

Ahmad Farzan, Founder at Evaluat

About the author

Ahmad Farzan · Founder at Evaluat

Founder of Evaluat. Has spent years building and load-testing Adobe Commerce and Magento storefronts, and built Evaluat to test sites the way real browsers actually hit them.

More from Ahmad →

Common questions

FAQ

Why is my WooCommerce checkout so slow?

Cart, checkout, and my-account are non-cacheable by design, so they run PHP and MySQL on every request while the catalog is served from cache. On budget hosting, a small PHP worker pool and no persistent object cache make that path the first to slow under load. It is driven by hosting, caching, and plugins, all of which you control.

Does WooCommerce slow down WordPress?

It adds uncacheable paths and more plugin code, but WooCommerce scales with the right hosting, caching, and a disciplined plugin set. WooCommerce's own guidance puts no hard ceiling on it. The honest summary is that it is slow by default and fast when tuned.

What are WooCommerce cart fragments and do they slow my site?

Cart fragments are a script that uses WordPress admin-ajax to refresh the mini-cart without reloading the page. Before WooCommerce 7.8 in June 2023 the script was enqueued on every page; since then it loads by default only where the cart widget is rendered. Where it does run it is an uncached PHP request, so it adds origin work that grows with traffic.

How many concurrent users can WooCommerce handle?

There is no single number, because a store has two ceilings. The cached catalog serves thousands of visitors cheaply, while the uncacheable cart and checkout path is capped by your PHP worker pool and database. The honest answer comes from load testing the full journey on a staging copy of your own stack.

Can I load test my WooCommerce store?

Yes, and because WooCommerce is self-hosted you can test the whole journey including checkout, unlike a hosted platform. Test a staging copy that mirrors production with payments in sandbox, not the live site. Check your host's terms first, since many managed hosts restrict load testing; Kinsta, for example, prohibits it on its platform.

Do too many plugins slow down WooCommerce?

They can. Each active plugin can add database queries, hook callbacks, and front-end scripts that run on every request, so a store assembled from a dozen or more pays that cost repeatedly. This is the category cost, not a verdict on any single plugin. Measure which ones add the most weight rather than guessing.

What is wp_options autoload and why does it matter for WooCommerce?

WordPress loads every option flagged to autoload from the wp_options table on each page request. As settings and expired transients accumulate, that payload grows and taxes every page. Without a persistent object cache, the server reads those options from the database on every page view, so trimming the table and clearing stale transients helps.

Does an HTTP load test measure WooCommerce Core Web Vitals?

No. In HTTP mode a protocol load test reads response bytes without running a browser, so there is no Largest Contentful Paint, Interaction to Next Paint, or Cumulative Layout Shift to record. Measuring Core Web Vitals under load needs a real browser that executes the theme and plugin JavaScript the way a shopper's device does.

See it on your site

Test in real browsers.
Debug in real sessions.

Want to see this measured on your app?

30 minutes. We build a scenario on your real customer journey, run a small test, and walk you through the report with your data in it.