The Page You Can’t Cache
Every speed optimization guide starts the same way: install a caching plugin, enable a CDN, compress your images. And for 90% of your pages, that works. Homepage? Cached. Category pages? Cached. Product pages? Mostly cached.
The short answer: a WooCommerce checkout is slow because it’s one of the few pages that can’t be cached (cart and My Account are the others) — every request runs the full PHP stack at bare server speed. The latency comes from four layers: server CPU and OPcache, the database (autoloaded options, postmeta bloat), blocking external APIs (Stripe, shipping, tax), and frontend JavaScript. Measure each layer, find the dominant one, and fix that first.
Table of Contents
But your checkout page is different.
Checkout contains user-specific data — cart contents, shipping address, tax calculations, payment tokens. No caching plugin can touch it. Every single checkout load runs the full PHP execution pipeline, hits the database, calls external APIs, and renders the result from scratch.
Your checkout page runs at bare server speed. Always.
That’s why a store can score 95 on Lighthouse and still have an 8-second checkout. The cache hides the problem everywhere else. Checkout exposes it.
You’ve usually felt it earlier in the funnel, too: the moment a customer clicks Add to cart is the same kind of request — an uncacheable AJAX call that boots your whole stack — and it shows the same bare server speed.
And here’s the business case: Google and Deloitte’s “Milliseconds Make Millions” study found that a 100ms improvement in page load time produces an 8.4% increase in retail conversions. On checkout — the page where purchase decisions happen — every millisecond of latency is directly correlated with lost revenue.
How Bad Is It? Let’s Look at the Numbers
One r/woocommerce user ran a clean test: fresh WordPress install, default Twenty Twenty-Four theme, all plugins deactivated except WooCommerce itself. The wc-ajax=update_order_review call — the AJAX request that fires every time the checkout page updates — took 700-1100ms.
That’s nearly a full second of server response time with zero plugins, zero customization, and a stock theme. The overhead is coming from WooCommerce itself: stock checks, shipping calculations, tax logic, payment gateway validation. All of it runs on every checkout page load — even if you don’t use half of those features.
Now add what a real store looks like:
| Layer | Latency added | Source |
|---|---|---|
| WooCommerce core checkout logic | 700-1100ms | Reddit benchmark, minimal setup |
| Stripe payment gateway | +1000-2500ms | GitHub issue #1261, 3.5x slowdown vs. cash-on-delivery |
| Real-time shipping rates (USPS/UPS API) | +200-800ms | External API round-trip |
| Tax calculation (Avalara) | +100-500ms | External API round-trip |
| reCAPTCHA validation | +100-300ms | Google API round-trip |
| Order Attribution Tracking | +50-200ms | WooCommerce default since 8.5 |
Add those up. A “normal” WooCommerce checkout with Stripe, one shipping API, and a tax service is doing 2-5 seconds of server-side processing before the browser even starts rendering the response. And this is before we talk about frontend JavaScript.
One developer on r/woocommerce summed it up: “What’s the #1 thing that slows down your WooCommerce store?” — The top answer, with the most upvotes: “It’s WooCommerce itself.”
The Four Layers of Checkout Latency
Most guides treat checkout slowness as a single problem. It’s not. There are four distinct layers, and you need to diagnose which one is killing you before you can fix it.
Layer 1: The Server
PHP is single-threaded. When a customer clicks “Place Order,” WooCommerce processes the entire checkout sequence — validate cart, calculate taxes, create order, call payment gateway — on one CPU core, in one line. It can’t split this across multiple cores. It can’t parallelize.
This means the clock speed of that single core determines your checkout speed. A server with 8 cores at 2.3 GHz will process checkout slower than a server with 2 cores at 5.0 GHz. More cores handle more concurrent requests — but each individual checkout runs only as fast as one core can go.
Most managed hosting providers run older server-grade processors (Intel Xeon, AMD EPYC) at 2.0-2.5 GHz. These are designed for density — packing thousands of tenants onto shared hardware — not for single-threaded speed. A high-frequency AMD Ryzen 9 at 5.0+ GHz processes the same PHP code roughly twice as fast per request. That’s the difference between a 1.5-second checkout and a 0.75-second checkout. Physics, not software.
How to check your CPU: SSH into your server and run:
lscpu | grep "Model name\|MHz"
If you see “AMD EPYC 7xx1” at 2.0 GHz on a shared host — that’s a hardware ceiling no plugin can overcome.
OPcache is critical for checkout. OPcache stores pre-compiled PHP bytecode in memory so PHP doesn’t recompile scripts on every request. On cached pages, this barely matters — the page is served from HTML cache anyway. On checkout, it matters enormously, because every request runs the full PHP stack. We measured what an undersized OPcache does to a WooCommerce request, file by file, in 1,200 recompiles per page view — checkout is exactly the page where that cost lands.
One Reddit user discovered that two identical WooCommerce stores on the same hosting had drastically different checkout speeds. The difference? OPcache wasn’t enabled after a PHP version upgrade on the slower one. “This made such a big difference on AJAX functions (eg add to cart etc).”
Recommended minimum:
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.revalidate_freq=60
Layer 2: The Database
Every checkout triggers dozens of database queries. The two biggest offenders:
The autoloaded options problem. On every page load (including checkout), WordPress runs:
SELECT option_name, option_value
FROM wp_options
WHERE autoload = 'yes'
This loads every autoloaded option into memory. On a fresh WordPress install, that’s a few hundred KB. On a mature WooCommerce store with 30+ plugins, it can exceed 10MB. And here’s the real problem: the autoload column has no index by default. On a wp_options table with 50,000+ rows, this single query can take hundreds of milliseconds.
Check your autoload size:
SELECT SUM(LENGTH(option_value)) AS autoload_bytes
FROM wp_options
WHERE autoload = 'yes';
If it’s over 1MB, you have a problem. Over 5MB, it’s an emergency.
The postmeta bloat problem (legacy order storage). If you’re still using WooCommerce’s legacy order storage, every order creates 1 row in wp_posts and ~29 rows in wp_postmeta. A store with 50,000 orders has over 1.4 million rows in postmeta. The JOIN queries that WooCommerce runs during checkout slow to a crawl.
The fix: migrate to High-Performance Order Storage (HPOS). HPOS replaces the wp_posts/wp_postmeta model with optimized dedicated tables. WooCommerce’s own benchmarks show:
- 5x faster order creation
- 1.5x faster checkout throughput under concurrent load
- 40x faster admin order filtering
If you haven’t migrated yet, this is likely your single biggest database bottleneck.
Layer 3: External APIs (The Silent Killer)
This is the layer most optimization guides miss entirely.
During a typical checkout, WooCommerce makes multiple blocking HTTP calls to external services:
- Payment gateway — Stripe processes the charge (1-3 seconds round-trip)
- Shipping rates — USPS/UPS/FedEx APIs return real-time shipping options (200-800ms)
- Tax calculation — TaxJar or Avalara calculates tax in real-time (100-500ms)
- Address validation — Some setups validate the shipping address against external databases
- Fraud prevention — reCAPTCHA or similar services verify the customer
Each of these is a synchronous HTTP call. PHP waits for each response before continuing. They don’t run in parallel — they stack.
The compounding is brutal. Take a checkout with Stripe + USPS real-time rates + Avalara tax + reCAPTCHA:
Stripe API: ████████████████ 1800ms
USPS API: ██████████ 500ms
Avalara API: ████████ 400ms
reCAPTCHA: ████ 200ms
─────
Total blocking: 2900ms
That’s nearly 3 seconds of waiting — and none of it is your server’s fault. Your PHP code is literally idle, waiting for external services to respond. No amount of server optimization will fix this. You need to either reduce the number of external calls or find ways to make them non-blocking.
How to find hidden API calls: Install Query Monitor and check the HTTP API Calls panel on your checkout page. You may discover plugins making calls to dead endpoints — one developer found that old, forgotten plugins were making API calls to services that no longer existed, and the timeout on those calls was adding 5-10 seconds per request.
Layer 4: The Frontend
After the server finishes processing, the browser still needs to render the checkout page. This is where payment gateway JavaScript creates problems.
Stripe’s JavaScript overhead: The official Stripe plugin loads 3 stylesheets globally, injects scripts on product pages (not just checkout), creates an analytics iframe, and sets cookies — all without regard for consent APIs. One developer documented that after simply enabling the Stripe plugin, the checkout JS payload increased significantly and the payment buttons took 10-15 seconds to render on a budget host.
The checkout block paradox: WooCommerce’s newer block-based checkout loads approximately 300 KB of compressed JavaScript — roughly 3x larger than the classic shortcode checkout. The request count nearly doubles. However, WooCommerce’s own data shows a 27% increase in checkout conversions from the block checkout due to better UX. Faster isn’t always better if the UX is worse.
Theme overhead on checkout: Multipurpose themes (Flatsome, Woodmart, Divi) load their entire CSS/JS stack on every page — including checkout. Sliders, animations, megamenu scripts, font libraries. None of it is needed on checkout. One developer reported that switching from a commercial theme to a custom lightweight theme cut their checkout page load from 6 seconds to 2.8 seconds.
How to Diagnose Why Your WooCommerce Checkout Is Slow
Don’t guess. Measure — and measure in layers, from the bottom up, so that each number isolates one thing. A single “checkout TTFB” tells you checkout is slow. It doesn’t tell you who is slow: the server, your plugin stack, or checkout itself.
Three rules before you start:
- Measure server time, not page load. Everything below is time spent generating the response. Frontend rendering is a separate layer (Layer 4) with separate tools.
- Repeat every measurement five times and take the median. The first request after a deploy hits a cold OPcache and an empty object cache; a single outlier proves nothing.
- Bypass the page cache. Checkout is never cached, but your baseline pages are. Being logged in as an admin bypasses page cache on every mainstream cache plugin — and it’s what lets Query Monitor run. (An admin request carries extra overhead a customer never sees — the admin bar, Query Monitor itself, plugins doing admin-only work. It’s real, but it’s constant across Steps 2–4, so it cancels out in the differences. We ignore it.) Do all of this on a staging copy that’s functionally identical but externally dead if you can; profiling tools on a live checkout are a risk you don’t need.
Two instruments: curl for what happens before WordPress, Query Monitor for what happens inside it. Query Monitor’s Overview panel gives you page generation time, database query time and query count; its HTTP API Calls panel lists every external request with its duration — all for the exact request you’re looking at, unaffected by any cache.
Step 1: The infrastructure floor — PHP without WordPress
Create hello.php in your web root:
<?php echo microtime(true);
Measure it from the server itself, so network latency doesn’t pollute the number:
for i in 1 2 3 4 5; do
curl -s --resolve your-store.com:443:127.0.0.1 \
-w ' pre=%{time_pretransfer} start=%{time_starttransfer}\n' https://your-store.com/hello.php
done
Two details that matter. --resolve points the request at your own nginx instead of going out through Cloudflare or a load balancer and back. And the number you want is start minus pre: pre is the TLS handshake with your own server, start is when the first byte arrived — the difference is web server + PHP-FPM and nothing else. On our test box the raw TTFB was 30 ms, of which 29 ms was the handshake and 1 ms was PHP. Print the body too: the script echoes the current timestamp, so if the same value comes back twice, you’re reading a cache, not PHP.
Expect single-digit milliseconds. Tens of milliseconds means PHP-FPM is queueing or the host is overloaded; hundreds means your problem is infrastructure and nothing you do inside WordPress will fix it. Every number below sits on top of this floor. Delete the file when you’re done.
No SSH? Measure from the browser’s DevTools Network tab instead and accept that the number includes the round trip to you. It still works as a floor, as long as you take Steps 2–4 from the same place.
Step 2: The WordPress floor — your plugin and theme stack
Open an empty page — a blank draft page, or your privacy policy — while logged in, and read Query Monitor: page generation time, database query time, number of queries, and whether the HTTP API Calls panel is empty.
This request boots WordPress, loads every active plugin, initialises your theme and reads all autoloaded options — but runs no checkout logic. Whatever it costs, checkout inherits.
| Empty page generation time | What it tells you |
|---|---|
| < 150 ms | Clean stack. Checkout slowness is checkout-specific — go to Step 3. |
| 150–400 ms | Typical for a store with 30+ plugins. Note the number; it’s your baseline. |
| > 400 ms | Your stack is slow before WooCommerce does anything. Checkout will never be fast until this is. |
If it’s slow, Query Monitor already tells you which layer it is:
- Database query time dominates → Layer 2: autoloaded options, plugins querying on every page, duplicate queries.
- HTTP API calls on an empty page → Layer 3: license checks, analytics pings, plugins phoning home on every request.
- Neither — the time is just PHP → CPU-bound code: too many plugins, a heavy theme, or a slow core with OPcache off. That’s Layer 1, in two flavours — “too much code” and “too slow a CPU” — and only a PHP profiler (Code Profiler,
wp profile) tells them apart. Checklscpuand OPcache status first; they’re free.
Step 3: The checkout page — what WooCommerce adds
Add a product to the cart, open /checkout/, read the same numbers. Subtract Step 2. The difference is the cost of checkout itself: session and cart totals, shipping and tax calculation, payment gateway initialisation, the checkout template.
| Checkout minus empty page | Diagnosis |
|---|---|
| < 300 ms | Checkout logic is lean. If checkout still feels slow, it’s Step 4 or the frontend. |
| 300–800 ms | Normal for stock WooCommerce plus one payment gateway. |
| > 800 ms | Something checkout-specific is expensive — the panels below say what. |
One caveat: with the block checkout, the page itself is a thin shell rendered in the browser, so expect a small delta here — the real work moves to the Store API requests in Step 4. With the classic shortcode checkout, the page does the work and the delta shows it.
Then look at where the extra time went:
- HTTP API calls appear, or their total grows → Layer 3. Shipping-rate APIs, tax services, gateway handshakes, anti-fraud. The panel lists each call with its duration — sort by time.
- Database query time jumps and the new queries come from orders,
postmetaor_wc_session_→ Layer 2. Legacy order storage or session bloat; HPOS is the fix. - Neither — PHP time grows and Queries by Component points at one plugin → that plugin is doing heavy work on checkout: dynamic pricing, discount rules, address validation. Deactivate it on staging, re-measure, confirm.
Step 4: The update_order_review request — what the customer feels
The checkout page loads once; the recalculation request fires every time the customer changes a field. Which request depends on your checkout:
- Classic (shortcode) checkout:
wc-ajax=update_order_review. - Block checkout: the Store API —
POST /wc/store/v1/batch(andcart/select-shipping-rate,cart/update-customer), fired byapi-fetch. Filter the Network tab bywc/store.
Change the shipping method five times and note the Waiting (TTFB) of each response.
This request recalculates totals, shipping and tax — no template, no theme. Compare it with the checkout page time from Step 3:
- Close to the page time → the checkout logic is the cost (shipping and tax APIs, cart calculations), not rendering. Layer 3, or the plugin you found in Step 3.
- Far below it → rendering is what’s slow: theme templates, the checkout block, hooks firing on
woocommerce_checkout_*. That’s your theme or a front-end-heavy plugin.
Stock WooCommerce with no plugins already spends 700–1100 ms here (the benchmark at the top of this article), so treat anything under 1 s as normal, 1–2 s as worth fixing, and over 2 s as an emergency.
Reading the numbers together: a real store
Measured on 30 August 2026 on loftlight.pl, a WooCommerce store we look after. Setup: dedicated Hetzner server, Intel Core i5-13500 (up to 4.8 GHz), GridPane, nginx + PHP-FPM 8.3 (pm.max_children = 30), OPcache on (2 GB, 131k files), behind Cloudflare. WordPress 7.1, WooCommerce 11.0.1, 37 active plugins, Flatsome child theme, HPOS enabled, block checkout. Step 1 via curl from the server to localhost; Steps 2–3 logged in as admin with Query Monitor; Step 4 from DevTools. Five samples each — median shown, all samples in brackets.
| Step | Median | Samples |
|---|---|---|
1. hello.php, server time (starttransfer − pretransfer) | 1.1 ms | 1.1 / 1.4 / 0.9 / 1.1 / 1.0 |
| …of which local TLS handshake, not PHP | 29 ms | — |
| 2. Empty page — generation time | 204 ms | 181 / 204 / 232 / 193 / 274 |
| database time | 19 ms | 12 / 19 / 39 / 25 / 14 |
| queries | 151 | 139 / 151 / 156 / 146 / 151 |
3. Checkout page (/zamowienie/) — generation time | 223 ms | 215 / 219 / 376 / 223 / 229 |
| database time | 18 ms | 12 / 18 / 40 / 14 / 19 |
| queries | 159 | 159 / 154 / 159 / 152 / 159 |
4. Shipping method change → Store API batch (block checkout) | 276 ms | 276 / 217 / 316 / 310 / 236 |
Query Monitor’s HTTP API Calls panel was empty on both the empty page and the checkout — “No HTTP API calls” on either request. Nothing in the loop phones home, and there’s no Stripe in the stack.
Bottom-up:
- Infrastructure: 1 ms. The floor is effectively zero. Note the trap: raw TTFB from
curlwas 30 ms, and 29 of those were the TLS handshake with our own nginx. Measure the difference, or you’ll blame PHP for your certificate. - WordPress stack: ~200 ms. 37 plugins, 151 queries — but only 19 ms of database time. The remaining ~180 ms is PHP: plugin initialisation, the theme, autoloaded options being unserialised. That is the single biggest fixed cost on this store, and it’s paid on every uncached request. It’s also CPU time on a 4.8 GHz core; on a 2.0–2.5 GHz shared host the same code would cost roughly twice as much before WooCommerce did anything.
- Checkout: +19 ms, +8 queries, zero HTTP calls. Almost nothing — because this is block checkout, and because nothing in the loop phones home. The page is a shell; the work moves to the Store API, which is Step 4.
- Shipping recalculation: 276 ms, including an ~80 KB JSON response. The entire checkout interaction the customer feels is under 300 ms. Compare that with the 700–1100 ms a stock, plugin-free WooCommerce spent on the same job in the Reddit benchmark at the top of this article: same WooCommerce code, different hardware. Physics.
Verdict: nothing to fix on Layers 1–3. The only meaningful lever left is the ~180 ms of plugin PHP in every request — the “too much code” flavour of Layer 1, which is profiler territory, and a nice-to-have at this speed. If this store ever “felt slow” at checkout, the next place to look would be Layer 4 — Flatsome plus block-checkout JavaScript — not the server.
How to Fix a Slow WooCommerce Checkout (Layer by Layer)
Based on the diagnosis above, here’s what to fix — in order of impact. None of it is “install a caching plugin.”
If your TTFB is over 1 second: fix the server first
No plugin can overcome slow hardware. If your server runs at 2.0 GHz on shared infrastructure, the checkout will be slow regardless of what you optimize. The single highest-impact change is moving to dedicated high-frequency CPU — which is why we argue hosting should be step one, not the last resort. We’ve benchmarked WooCommerce checkouts on AMD Ryzen 9 (5.0+ GHz) vs standard Xeon hosts — the difference is typically 40-60% faster server response time, which translates directly to checkout speed.
If Query Monitor shows 200+ database queries: fix the database
- Migrate to HPOS if you haven’t. 5x faster order creation is not a marginal improvement.
- Audit autoloaded options. Disable autoload on everything that isn’t needed on every page.
- Clean orphaned data.
_wc_session_entries, transients, revision postmeta — these accumulate silently.
If external API calls dominate: reduce blocking calls
- Use flat-rate or table-rate shipping instead of real-time carrier APIs where possible. Real-time rates look nice but add 200-800ms per checkout. If your shipping is predictable, pre-calculate it.
- Evaluate your tax solution. TaxJar responds in under 20ms. Avalara can take hundreds of milliseconds. If you’re using Avalara, consider whether the additional complexity is worth the latency.
- Audit payment gateway plugins. Run checkout with each gateway individually and compare timing. Stripe’s documented 3.5x overhead is gateway-specific — alternatives may be faster for your use case.
- Remove dead endpoints. Query Monitor’s HTTP API panel reveals plugins calling APIs that no longer exist. The timeout on those calls can add 5-30 seconds.
If frontend JavaScript is the bottleneck: audit what loads on checkout
- Disable plugins per page. Tools like Asset CleanUp or Perfmatters let you prevent non-essential plugins from loading CSS/JS on checkout. Your countdown timer, your popup plugin, your slider — none of them belong on checkout.
- Evaluate the checkout block vs shortcode. Block checkout is heavier (3x JS) but converts 27% better. Test both and measure conversion rate, not just page speed.
The Uncomfortable Truth
Most WooCommerce speed guides are written by hosting companies or plugin vendors. Their advice naturally gravitates toward what they sell — “upgrade your hosting plan” or “install our optimization plugin.”
The reality is more nuanced. Checkout latency is a compounding problem across four distinct layers, and the fix depends entirely on which layer is your bottleneck. A caching plugin won’t help because checkout can’t be cached. A faster CDN won’t help because the latency is server-side. And “disabling plugins” is a blunt instrument that often breaks functionality you need.
The approach that actually works: measure each layer independently, identify the dominant bottleneck, and fix that first. Then move to the next layer.
Every update we deploy through WooCommerce Care goes through this exact diagnostic process. We profile the checkout, identify where the milliseconds are hiding, and eliminate them layer by layer — server tuning, database optimization, API audit, frontend cleanup. Then we verify with automated Playwright tests that the entire purchase flow still works end-to-end (here’s how to write your first one).
If your checkout takes more than 2 seconds and you’re not sure where the latency lives — that’s exactly what we diagnose.
FAQ: Slow WooCommerce Checkout
Why is my WooCommerce site slow only during checkout?
Because checkout is one of the pages your cache can’t serve — cart and My Account are the others — and it’s the one where money changes hands. Homepage, category and product pages come from static HTML cache; checkout holds user-specific data (cart, address, taxes, payment tokens), so every load runs PHP, the database and external payment, shipping and tax APIs from scratch. Your site isn’t fast — your cache is. Checkout is where you see the real server speed.
How do I fix a slow WooCommerce checkout page?
Measure in layers before you touch anything: a bare PHP file (infrastructure), an empty page (your plugin stack) and the checkout page (what WooCommerce adds), each with Query Monitor open and repeated five times. Then fix the dominant layer: the server (CPU clock speed, OPcache) if TTFB is over 1 second; the database (HPOS, autoloaded options) if you see 200+ queries; external APIs (real-time shipping, tax, dead endpoints) if HTTP calls dominate; the frontend (theme and plugin assets) if the server is fast but rendering is slow.
Why is WooCommerce checkout very slow with Stripe?
The official Stripe gateway adds a synchronous API round-trip to every order — documented at roughly 3.5x the time of cash-on-delivery in GitHub issue #1261 — and loads its scripts, stylesheets and an analytics iframe on the frontend. On a slow host that can mean payment buttons taking 10+ seconds to render. Test checkout with each gateway individually and compare the wc-ajax=checkout timing.
Is WooCommerce add to cart slow for the same reason?
Yes. Add to cart is a POST to wc-ajax=add_to_cart (or the Store API on block themes) that WooCommerce sends with no-cache, no-store headers: it boots the whole plugin stack, validates the product, writes the session and renders the mini-cart, and no page cache can serve it. It’s the first uncached request most customers ever make, so it’s usually where they first feel the server. We break that request down step by step in WooCommerce Add to Cart Slow? Anatomy of the Request No Cache Can Save.
Does a caching plugin speed up WooCommerce checkout?
No. Caching plugins store rendered HTML for anonymous visitors; checkout is dynamic and excluded from page cache by design. What helps on checkout is OPcache (bytecode cache), an object cache like Redis for repeated database reads, and fewer blocking API calls — not page caching.
Checkout Speed Diagnostic Checklist
Before optimizing, measure — in this order:
hello.phpTTFB from the server (median of 5) — infrastructure floor- Empty page, logged in, Query Monitor: generation time, query count, DB time, HTTP API calls — WordPress floor
- Checkout page, the same four numbers — and the delta over the empty page
wc-ajax=update_order_reviewTTFB (Network tab, 5 samples) — and how it compares to the checkout page time- Autoloaded data size (SQL query above)
- CPU model and clock speed (
lscpu) - OPcache status (
php -i | grep opcache) - HPOS migration status (WooCommerce > Settings > Advanced > Features)
- JavaScript payload size on the checkout page (Layer 4 — separate tools)