Why WooCommerce is slow
WooCommerce adds dozens of AJAX requests, a cart and sessions — none of which can be cached with standard methods. A special approach is required.
Disabling unnecessary scripts
// functions.php — drop the WooCommerce scripts on non-shop pages
add_action('wp_enqueue_scripts', function() {
if (!is_woocommerce() && !is_cart() && !is_checkout()) {
wp_dequeue_style('woocommerce-general');
wp_dequeue_script('wc-cart-fragments');
}
}, 99);
Image optimization
| Tool | Method | Size reduction |
|---|---|---|
| ShortPixel | WebP + compression | 60–80% |
| Imagify | WebP + AVIF | 50–75% |
| cwebp CLI | WebP manually | 40–70% |
Database indexes for WooCommerce
-- Speeding up queries against product meta
ALTER TABLE wp_postmeta ADD INDEX meta_key_value (meta_key, meta_value(20));
-- Speeding up order history
ALTER TABLE wp_woocommerce_order_items ADD INDEX order_item_type (order_item_type);
Common WooCommerce errors
The specific thing about WooCommerce is that performance problems show up not on the storefront but exactly where the money moves: the cart and checkout. An error on the storefront loses a visitor; an error on checkout loses an order that was almost paid for — so diagnostics should start with the payment chain itself.
| Error text or symptom | Cause | What to do |
|---|---|---|
| Error establishing a database connection at checkout under peak load | The MySQL concurrent connection limit is exhausted by a flood of cart AJAX requests | Raise max_connections and enable fragment caching only for unauthenticated guests |
| There has been a critical error on this website after a WooCommerce update | A theme or payment plugin version conflicts with the new WooCommerce API | Roll back the update on staging and check wp-content/debug.log |
| 500 error on /wp-json/wc/store/cart | A WAF or security plugin rule blocks the WooCommerce REST API by the wp-json pattern | Add an exception for /wp-json/wc/ in the firewall rules |
| The cart shows someone else's items after caching is enabled | A full page cache at the web server cached the cart HTML along with one visitor's data | Exclude /cart/, /checkout/ and /my-account/ from the cache zone |
If the problem is specifically the database connection limit, see the article on MySQL: too many connections.
When regular tuning will not save you
- A catalog above 5000 products with attribute filters — MySQL LIKE queries do not scale at that volume; you need external search like Elasticsearch
- More than 50 concurrent orders a minute during a sale — a single PHP-FPM pool cannot keep up; you need a background job queue and several workers
- Multi-currency and multi-warehouse add-ons from third parties — they add their own heavy queries that the indexes in this article do not fix
- Hosting with a network disk under wp-content/uploads — I/O will be slow regardless of caching and indexes
- A subscription model with daily auto-charges for thousands of customers — WordPress cron (wp-cron) on ordinary hosting is unreliable under that load; you need a real system cron
It is also worth checking the hosting plan's CPU core count separately. WooCommerce is, first of all, PHP load on every cart AJAX request, and without spare CPU capacity even correct indexes will not prevent PHP-FPM queues during a sale.
Excluding critical pages from the cache
A full HTML cache cannot be applied to the dynamic store pages. At the web server level this is done with an explicit path exception:
location ~* ^/(cart|checkout|my-account)/ {
proxy_no_cache 1;
proxy_cache_bypass 1;
}
For repeating parts inside those same pages (for example, a recommendations block), use an object cache instead — see the article on Redis for WordPress, rather than caching the whole page.
Checking store performance
- Checkout TTFB should stay stable under 10+ parallel test sessions
- The Query Monitor plugin shows the number of SQL queries per product page — more than 100 queries points to a problem in the theme or add-ons
- After adding indexes, EXPLAIN on a slow query should show key usage instead of a full table scan
- The GTmetrix waterfall for /cart/ should not contain requests longer than 500 ms to its own domain
-- Check that the index is actually used
EXPLAIN SELECT * FROM wp_postmeta
WHERE meta_key = '_stock' AND meta_value < 10;
SHOW INDEX FROM wp_woocommerce_order_items;