The caching plugin is installed, the images are compressed, yet the WooCommerce store is still slow: storefront pages take several seconds to open, checkout hangs and managers wait for the order list to load. Below we cover where a store loses time (the database, the background task queue, the server), how to check it with standard WordPress and WooCommerce tools and what you can fix without a developer.
Where it slows down: storefront, cart or admin
A WooCommerce store has four typical slowdown points, and each has its own symptom.
- The home page, categories and product pages open slowly even for guests. Measure the server response time: in Chrome, press F12 → Network → the request for the page itself → Timing, the Waiting for server response line (in the Chrome documentation it is Waiting (TTFB)). If the server takes a long time to respond, look for the cause in catalog database queries, filters and plugins. If the response is fast but PageSpeed complains about scripts and images, the problem is in the frontend. That is covered in our article on Google PageSpeed Insights.
- Browsing the catalog is fine, but the cart and checkout lag. These pages cannot be cached, every request to them goes through PHP and the database, and on top of that come shipping calculation, payment modules and calls to external services.
- A slow WooCommerce admin: the order list, the order page, reports. The database is usually to blame: the old way of storing orders (see the HPOS section below), the background task queue or oversized tables. Less often, it is plugins that call external services on the order screen. There is also a less visible load: every open WordPress admin tab contacts the server every 15–120 seconds through the Heartbeat API; five managers with a dozen tabs each create a constant stream of requests.
- Everything works, but at certain hours the site grinds to a halt. Look for scheduled tasks: price and stock exports from the accounting system, imports, bulk background tasks, server backups.
Write down on which pages and at what times the store is slowest: this list will show a developer where to start.
Measurement tools
Start with the built-in tools. Since version 6.6, Tools → Site Health in WordPress warns you if the size of autoloaded options exceeds 800 KB. There, WordPress also checks whether a persistent object cache and a page cache are in place; the page cache check treats a server response slower than 600 ms as a problem. Both cache checks work only on the live site and will not appear on a staging copy. WooCommerce adds its own items here, including one on order storage synchronization.
WooCommerce → Status has a system status report with a Database section and the size of each table. It shows what has grown: wp_options, wp_postmeta, the actionscheduler_* tables or wp_woocommerce_sessions (the wp_ prefix may be different on your site). Next to it is the Scheduled Actions tab with the background task queue.
The free Query Monitor plugin shows database queries on the open page, flags slow, duplicate and failed ones and groups them by plugin and theme, so you can see which plugin takes the most time. It also lists external HTTP requests with their response times: on the checkout page, these are the seconds spent waiting for the shipping service. The number of plugins on its own means little: one plugin that calls an external service on every page does more harm than ten well-written ones. Install Query Monitor for the duration of diagnostics (preferably on a staging copy) and remove it afterward. Disabling plugins one by one is also acceptable only on a staging copy: on a live store, a disabled payment or shipping module breaks orders.
On the server, the MySQL slow query log comes in handy. It is off by default, and the long_query_time threshold is 10 seconds, which is a lot for a website, so the threshold is lowered while measurements are taken. MySQL settings are changed at the server level (on shared hosting, through the hosting provider’s support; on a VPS, through the administrator).
HPOS order storage: what changed in 2026
For a long time, WooCommerce stored orders the same way WordPress stores blog posts: the main order row in the wp_posts table and each field (address, phone, payment method, waybill number) as a separate row in wp_postmeta, mixed in with the data for products, pages and everything else. The more orders and products there are, the longer it takes the database to find the right row in the shared table.
HPOS (High-Performance Order Storage) moves orders to separate tables, wc_orders, wc_order_addresses, wc_order_operational_data and wc_orders_meta, with indexes designed for typical store queries. HPOS has been enabled by default for new installations since WooCommerce 8.2 (October 2023). WooCommerce does not migrate stores launched earlier on its own: they are switched over manually.
You can see the current mode in WooCommerce → Settings → Advanced → Features, in the order storage item. There are three options:
- The old storage based on WordPress posts. Orders are in
wp_postsandwp_postmeta. - Compatibility mode. WooCommerce keeps orders in both storages and duplicates every change. The primary one can be the old storage (this is how the migration starts) or HPOS (this is how things are checked after the switch). The mode is needed during the migration so that you can roll back.
- HPOS without synchronization. The final state.
What versions 10.7 and 10.8 changed
WooCommerce 10.7 (released April 14, 2026) disabled sync on read by default. Previously, in compatibility mode, WooCommerce would pull into HPOS any data that a plugin had written directly to wp_posts or wp_postmeta when an order was opened. Now it does not: if an old-style shipping plugin saves the waybill number with the update_post_meta function, that number may not appear in the order under HPOS. A developer can restore the previous behavior with the woocommerce_hpos_enable_sync_on_read filter (a WordPress hook), but the WooCommerce team warns that this mechanism may be removed entirely in future versions and advises rewriting the integration to use the standard order functions (change announcement).
WooCommerce 10.8 (May 26, 2026) added indexes to wc_orders and wc_reserved_stock, restored the meta_value column in the wc_orders_meta table index (fixing slow searches by order metadata) and reduced the number of queries in the cart, product archives and the REST API. Version 10.8 requires WordPress 6.9 or later: a store on an older core will not get these improvements.
Should you switch to HPOS?
If your store is still on the old storage, we recommend planning the migration: the order query optimizations in 10.7 and 10.8 were built for the HPOS tables, and these are the tables WooCommerce will keep developing. The steps:
- Make a full staging copy of the site on a separate server.
- Check plugin compatibility. WooCommerce shows a warning on the Features screen and in the plugin list if a plugin is declared incompatible with HPOS. Plugins without such a flag also need to be checked manually, first of all shipping, payments and data exchange with the accounting system.
- Enable compatibility mode and wait until background tasks migrate the old orders (one task migrates 25 orders). In a large store, a developer runs the migration with the
wp wc hpos synccommand, as described in the official guide for large stores. - Switch to HPOS, keep synchronization on while you check everything, then turn it off.
Do not start the migration on a live store without a backup. The most time-consuming part is checking integrations; the switch itself takes a few clicks. In the same guide, WooCommerce says that in their tests, having synchronization on (while the old storage remained primary) did not noticeably slow down the store, so there is no reason to turn it off for speed; it is turned off after all plugins have been checked.
Bloated wp_options: autoload and transients
WordPress and plugins store settings in the wp_options table. Each option has an autoload flag: WordPress loads such options at startup on every request, whether the page needs them or not. The WordPress documentation warns that excessive autoloading reduces performance, especially if the options are rarely used.
Autoload is usually bloated by plugins. Some write logs and caches to options. Others create an option for every order: in May 2026, the wordpress.org forum discussed a shipping plugin that had created hundreds of autoloaded options named like _boxnow_parcel_order_id_*. Still others leave their data in the database after they are removed. Since WordPress 6.6, new options larger than 150 KB are not autoloaded by default, but whatever accumulated earlier is still there.
The sign of a problem is a Site Health warning about exceeding 800 KB. Next, a developer finds the largest options, identifies the plugin they belong to and decides whether to disable autoload, remove the leftovers of an uninstalled plugin or replace the plugin itself. Bulk-deleting options that “look like junk” with cleanup plugins is risky: that is how the settings of a working payment module get wiped.
Transients are a temporary cache that WordPress stores in the same wp_options table by default. They do have an expiration time, but WordPress does not clean up expired entries often, and in a store with a large catalog they pile up. There is a safe button for them in WooCommerce → Status → Tools, the item about expired transients. If a persistent object cache (Redis or Memcached) is connected on the server, transients are stored in memory and the options table does not grow because of them.
Background tasks: Action Scheduler and WP-Cron
A lot happens in a store in the background: sending some of the emails, webhooks, importing data into the Analytics section, rebuilding product lookup tables, migrating orders to HPOS, tasks from subscription and integration plugins. WooCommerce puts all these tasks into the Action Scheduler queue. The queue is stored in tables with the actionscheduler_ prefix, and you can view it in WooCommerce → Status → Scheduled Actions or in Tools → Scheduled Actions.
By default, the queue is run by WP-Cron no more than once a minute; each run picks up 25 tasks and has up to 30 seconds to process them. WP-Cron itself fires only when site pages are opened: at night, with no visitors, tasks wait, and in the morning they run at the same time as customer requests. Action Scheduler deletes completed and canceled tasks after 31 days, and since version 4.0 it keeps failed tasks for three months. Problems start in three cases:
- Tasks are created faster than they are completed. There are thousands of entries with the Pending status, some of them overdue, emails go out late and order statuses are not updated right away.
- Some plugin keeps creating tasks that fail. The
actionscheduler_actionstable and theactionscheduler_logslog grow by hundreds of thousands of rows, and the admin gets heavier. - The table is corrupted. In summer 2026, a case was described on the wordpress.org forum: the server ran out of space during a site migration, and MySQL flagged
actionscheduler_actionsas corrupted (marked as crashed). Tasks failed without any visible errors, and the store analytics stopped updating.
What you can do yourself: open Scheduled Actions, filter tasks by the Pending (waiting to run) and Failed (error) statuses and look at the column with the hook name, which usually tells you which plugin creates the tasks. Do not delete pending tasks in bulk: they may include customer emails and subscription renewals.
The rest is work for a developer or a server administrator. For large sites, the Action Scheduler documentation recommends running the queue through WP-CLI rather than WP-Cron (performance recommendations). In practice, this comes down to three actions: disable WP-Cron runs on page loads with the line define( 'DISABLE_WP_CRON', true ); in wp-config.php, set up a system cron on the server and clean up old entries with the wp action-scheduler clean command. We do not recommend increasing the number of concurrent queue runners without measurements: the same documentation warns that this can bring the site down.
Cart, sessions and page cache
A page cache saves ready-made HTML and serves it to the next visitors without building the page again or querying the database. This works well for the catalog. WooCommerce requires the cart, checkout and My Account pages to be excluded from the cache, because these pages contain a specific customer’s data (documentation on configuring caching plugins). In addition, a customer with an item in the cart gets the woocommerce_items_in_cart, woocommerce_cart_hash and wp_woocommerce_session_… cookies, and many caching systems use these cookies to serve pages to the visitor bypassing the cache. So a caching plugin speeds up only catalog browsing by guests, while checkout speed, the moment when the store makes money, depends on PHP, the database and the shipping and payment plugins.
Cart fragments: a request on every page
The wc-cart-fragments script updates the mini cart in the header: after the page loads, the browser sends the AJAX request ?wc-ajax=get_refreshed_fragments, which is not cached. Since WooCommerce 7.8, the script is loaded only together with the mini cart widget, but many themes load it on their own. To check, open any store page in Chrome, press F12 and type get_refreshed_fragments into the search on the Network tab. If the request is there, every page view sends an extra request to PHP that bypasses the cache; if the request is also slow to respond, the cart counter in the header updates with a delay. You can disable the script only after checking that the cart counter will keep updating. Sometimes it is easier to replace the theme’s mini cart with the Mini Cart block.
Customer sessions
WooCommerce stores customer sessions, together with cart contents, in the wp_woocommerce_sessions table. A guest session lasts two days by default and is then deleted by a background task (in current WooCommerce versions, from the same Action Scheduler queue). If the queue is stuck, the sessions table grows. It also grows because of bots that follow “Add to cart” links: each visit creates a session. WooCommerce → Status → Tools has a button for clearing sessions, but it empties the carts of all current customers, so use it only at a quiet time and with a clear understanding of the consequences.
External requests at checkout
When the address or shipping method changes, the checkout page recalculates the cost. If the shipping module calls the carrier’s API on every recalculation and does not cache the responses, the customer waits for someone else’s server to respond. Query Monitor shows such delays in the HTTP requests section. The fix is to improve the module: cache the lists of branches and rates, set timeouts and load the reference lists on a schedule.
Catalog, filters and accounting data exchange
Take a store with variable products and a filter with twenty attributes. Each variation (size, color) is stored in the database as a separate product_variation record with its own price, stock and attributes. If the filter shows the number of products next to each value, it recalculates these numbers for every value of every attribute on each click. Without suitable indexes, these counts become the heaviest queries on the site.
To speed up filtering, WooCommerce has a product attributes lookup table; third-party filter plugins build their own indexes. Open a category page with filters applied in Query Monitor and see which queries take the longest and which plugin makes them. If the filter plugin is the bottleneck, configuring its indexing or replacing the plugin will help. With the built-in filter, a developer checks whether the attributes lookup table is populated and whether its use is enabled.
Data exchange with the accounting system is a separate topic. A typical setup: every few minutes, a full price list with all stock levels is uploaded to the site. Each product save clears its transients, queues a task to update the attributes table and triggers the handlers of every plugin subscribed to this event. The store slows down in waves, on the export schedule. We recommend transferring only the items that have changed, moving full exports to night hours and running them with a system cron or from the command line, without web requests.
Hosting: PHP version, object cache, resources
Hosting is checked after diagnostics, not instead of them. Moving to a VPS will not fix a query that goes through hundreds of thousands of rows: the same query will run a little faster, and that is all.
- PHP version. WooCommerce recommends PHP 8.3 or later. WooCommerce 11.6, scheduled for February 2027, will require at least PHP 8.1. Security support for PHP 8.2 ends on December 31, 2026; the migration is covered in our article on the end of PHP 8.2 support.
- Persistent object cache. Redis or Memcached keep in memory the data that WordPress would otherwise fetch from the database again on every request; transients go there too. Site Health will tell you if WordPress considers an object cache useful for a site of your size. On shared hosting, such a cache is often unavailable.
- OPcache. A PHP extension that keeps compiled code in memory so that PHP does not load and parse WordPress and plugin files on every request. On most hosting plans, OPcache is already enabled; if it is not, it is the first thing to enable. Ask your hosting provider or server administrator.
- Resources. The cart, checkout, admin, background tasks and bots are all served by the same PHP processes. When there are not enough processes, requests queue up and the whole store slows down at once. You can see this in server logs and load graphs; a single-page speed test will not show it.
What to fix yourself and what to delegate
On your own and without risk to your data:
- check Tools → Site Health and write down all performance warnings;
- find the largest database tables in WooCommerce → Status;
- open Scheduled Actions and count the tasks with the Pending and Failed statuses;
- check in Settings → Advanced → Features which order storage is enabled;
- find the
get_refreshed_fragmentsrequest in the browser and look at its response time; - find out the PHP version in the hosting control panel;
- remove plugins that have not been used for a long time (after first making sure that payments, shipping or data exchange do not depend on them);
- clear expired transients with the button in WooCommerce → Status → Tools.
For a developer or server administrator:
- migrating to HPOS and checking integrations that write orders directly to
wp_postmeta; - reviewing
wp_optionsand disabling unnecessary autoload; - cleaning up the Action Scheduler tables and moving the queue and WP-Cron to a system cron;
- the slow query log, indexes and catalog filter configuration;
- connecting an object cache, updating PHP and allocating server resources;
- reworking data exchange with the accounting system.
Before any action that changes the database, you need a fresh backup of the database and files, and a tested one, meaning a backup that can actually be restored. It is better to run changes on a staging copy first. One failed “optimization” of the orders table costs more than a month of a slow admin.
How RIVERIT helps with this
We take WooCommerce stores on for WordPress technical support: we update the core and plugins (usually monthly), make backups and monitor website availability. We respond to clients on support within one hour during business hours and fix critical failures outside business hours, if your plan includes this. We handle the diagnostics and fixes from this article, from migrating to HPOS to reworking integrations, as custom development. We take stores on a VPS or dedicated server on for administration: we analyze logs, configure the server and isolate websites from each other. If an old store is easier to rebuild than to fix, we offer online store development.













