WooCommerce cart not working: diagnosing a broken add-to-cart or checkout

Your WooCommerce add-to-cart button does nothing, the cart empties itself between pages, or the checkout page spins without completing the order. The cause is almost never 'a plugin conflict' in the way most forums suggest. In the majority of cases it is a caching configuration that serves a stale page where it should serve a dynamic one. This article walks through every common cause in order of likelihood, with a browser-side diagnostic for each.

The symptoms look different but share a root pattern: the browser sends an AJAX request to WooCommerce's ?wc-ajax= endpoint, and that request either never fires, fires and fails silently, or fires and gets a stale response. Once you know which of those three things is happening, the fix is usually obvious within minutes. The diagnostic path below goes from most common to least common.

If your cart fills correctly but the failure happens further down the funnel — a 403 at checkout submission, a missing payment method, a fatal PHP error during order placement — the WooCommerce checkout not working hub disambiguates the symptom and routes you to the right diagnostic.

Common symptoms

Three variations of the same underlying problem surface most often:

  • Add-to-cart button does nothing. You click, and nothing happens. No page reload, no cart count update, no error message. The product stays on the page as if nothing was clicked.
  • Cart empties between pages. You add a product, navigate to the cart page, and it says the cart is empty. Repeat. Same result.
  • Checkout spins forever. You fill in billing details, click "Place order", the spinner starts, and never stops. No order confirmation, no error. Sometimes the browser console shows an AJAX response of -1.

Each symptom points at a different failure point, but caching is the cause in more cases than everything else combined.

Caching: the most common cause by a wide margin

WooCommerce's cart, checkout, and my-account pages are session-specific and must never be page-cached. The cart shows one customer's items; the checkout contains a nonce token unique to that visitor's session. A cached version of either page serves the wrong data to the wrong person, or serves a stale nonce that WooCommerce rejects on submission.

WooCommerce sets the DONOTCACHEPAGE constant on cart, checkout, and my-account pages automatically. Every WordPress-level caching plugin that respects this constant (WP Rocket, WP Super Cache, W3 Total Cache, LiteSpeed Cache) will skip these pages. The problem starts when a cache layer sits outside WordPress and never sees the constant:

  • nginx fastcgi_cache caches PHP output before WordPress runs. It does not read PHP constants.
  • Varnish sits in front of the web server entirely. It needs explicit VCL rules to pass WooCommerce session cookies through.
  • Cloudflare APO caches HTML at the edge. Its WooCommerce integration handles exclusions, but the integration must be active, and custom checkout URLs are not excluded automatically.
  • LiteSpeed server-level cache is separate from the LiteSpeed Cache WordPress plugin and requires its own exclusion rules in .htaccess or the LiteSpeed admin.

How to check

Open the checkout page in an incognito window (a browser where you are not logged in). Open the Network tab in your browser's DevTools (F12 > Network), reload the page, and click the main document request (the one matching your checkout URL). In the Response Headers section, look for x-cache, cf-cache-status, age, x-proxy-cache, or x-nginx-cache.

If x-cache: HIT or cf-cache-status: HIT appears, or age: shows a number above 0, the checkout page is being served from a cache layer. That is the problem.

If you have SSH access:

curl -sI https://yoursite.nl/checkout/ | grep -Ei '^(x-cache|cf-cache-status|age|x-proxy-cache|x-nginx-cache)'

How to fix it

Add the WooCommerce session cookies to the cache bypass rules for your server-level cache. The three cookies to match:

Cookie Purpose
woocommerce_items_in_cart Tracks whether the cart has items
woocommerce_cart_hash Detects cart content changes
wp_woocommerce_session_* Links the browser to a server-side session

For nginx fastcgi_cache, the bypass rule in the server block looks like:

# Bypass cache when WooCommerce session cookies are present
if ($http_cookie ~* "woocommerce_items_in_cart|woocommerce_cart_hash|wp_woocommerce_session_") {
    set $skip_cache 1;
}

# Also bypass on specific WooCommerce pages by URL
if ($request_uri ~* "/cart/|/checkout/|/my-account/|\?wc-ajax=") {
    set $skip_cache 1;
}

For Cloudflare, add Page Rules (or Cache Rules in the new dashboard) that set "Cache Level: Bypass" on /checkout/*, /cart/*, /my-account/*, and /*?wc-ajax=*.

After applying the exclusion, clear the full cache and test again. You know it worked when the checkout page returns x-cache: MISS or cf-cache-status: BYPASS on every request, and the age header is absent or 0.

For a deeper look at why cache bypass rules fail and how to diagnose hit rates, why your WordPress cache is not working covers the full diagnostic path.

Stale nonce on a cached checkout page

This is a specific variant of the caching problem that deserves its own section because the symptom is different: the checkout page loads fine, but clicking "Place order" returns an AJAX response of -1 or a "security check failed" message.

WordPress nonces use a tick-based expiry system. The default lifetime is 24 hours, divided into two 12-hour ticks. A nonce created at the start of a tick lives the full 24 hours; one created at the end lives just over 12 hours. WooCommerce's checkout uses the woocommerce-process-checkout-nonce nonce, embedded in the checkout form HTML.

When a server-level cache serves a checkout page from its store, the nonce in the HTML is frozen at the moment the cache was created. If the cache lives longer than 12 hours (or straddles a tick boundary), the nonce is already expired or in its second tick when a customer sees it.

The misconception: "just refresh the page". Refreshing does not help when the server cache is the cause, because the refresh fetches the same cached HTML with the same stale nonce. The customer gets stuck in a loop. The fix is the same as above: exclude checkout from the cache layer entirely and clear the stale cached copy.

How to confirm it is a nonce issue

  1. Open the checkout page, fill in details, and click "Place order".
  2. Open the browser's Network tab (F12 > Network) and find the ?wc-ajax=checkout request.
  3. Click on it and read the response body. If it contains "result":"failure" and the message references a security check or the raw response is -1, the nonce is invalid.
  4. Compare the nonce value in the form's hidden field (woocommerce-process-checkout-nonce) against a fresh one generated by opening the checkout in a new incognito session. If they are identical, the page is cached.

Mixed content blocking the wc-ajax endpoint

If your site runs on HTTPS but the WordPress Address or Site Address in Settings > General still contains http://, or if a reverse proxy terminates SSL and does not pass the correct headers to WordPress, the ?wc-ajax= requests fire over HTTP while the page is HTTPS. Modern browsers block this as mixed content silently.

How to check

Open the browser console (F12 > Console) on any page with an add-to-cart button. If you see:

Mixed Content: The page at 'https://yoursite.nl/shop/' was loaded over HTTPS,
but requested an insecure XMLHttpRequest endpoint 'http://yoursite.nl/?wc-ajax=add_to_cart'.
This request has been blocked; the content must be served over HTTPS.

That is the cause.

How to fix it

  1. Go to Settings > General and confirm both WordPress Address and Site Address use https://.
  2. If you are behind a reverse proxy (nginx, Varnish, Cloudflare, a load balancer) that terminates SSL, confirm the proxy passes X-Forwarded-Proto: https and that wp-config.php contains the following snippet. You can edit wp-config.php through your hosting panel's file manager or via SFTP:
// Trust the X-Forwarded-Proto header from the reverse proxy
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
    $_SERVER['HTTPS'] = 'on';
}
  1. After fixing the URL settings, flush permalinks (Settings > Permalinks > Save) and clear any cache.

For a full walkthrough of finding and fixing mixed content resources beyond just the AJAX endpoint, mixed content warnings in WordPress covers the complete diagnostic path.

JavaScript conflict preventing the AJAX call from firing

When a plugin or theme script throws a JavaScript error before WooCommerce's own scripts execute, the add-to-cart AJAX handler never binds, or the checkout submission handler never fires. The button click does nothing because no listener is attached.

How to diagnose

  1. Open the browser console (F12 > Console).
  2. Click the add-to-cart button or the "Place order" button.
  3. Look for red error messages. A conflict typically shows as an Uncaught TypeError or Uncaught ReferenceError pointing at a file that is not part of WooCommerce (a theme script, a slider plugin, an analytics tag).

How to isolate

  1. Switch to a default theme (Storefront or Twenty Twenty-Five) temporarily. If the problem disappears, the theme's JavaScript is the cause.
  2. If the theme is not the cause, disable plugins one at a time. Start with plugins that inject front-end JavaScript: sliders, popups, analytics, page builders, and cookie consent tools. Re-test after each deactivation.
  3. The plugin whose deactivation fixes the button is either throwing an error or enqueuing a script that conflicts with WooCommerce's jQuery dependency chain.

If you find the conflicting plugin, check for an update first. If no update fixes it, contact the plugin developer with the exact console error. Do not leave it disabled permanently if it serves a business function; the error is a bug, not a configuration choice.

Cart fragments not loading after WooCommerce 7.8

Since WooCommerce 7.8, the cart-fragments.js script is only enqueued on pages where the Mini Cart widget is active. Before 7.8, this script fired on every page, sending an AJAX request to ?wc-ajax=get_refreshed_fragments on every page load to keep the mini-cart item count current.

If your theme uses a custom mini-cart implementation (hand-coded HTML that shows the cart count in the header, rather than the WooCommerce Mini Cart widget), upgrading past WooCommerce 7.8 breaks the cart count update silently. The mini-cart stops refreshing and shows stale or zero items. Clicking add-to-cart works at the server level, but the visual feedback never arrives because the fragment script is not loaded.

How to check

View the page source (Ctrl+U) and search for cart-fragments. If the script tag is missing, the fragment system is not active on that page.

How to fix it

Add this to your theme's functions.php (editable via Appearance > Theme File Editor in wp-admin, or through your hosting panel's file manager) or a site-specific plugin:

// Re-enqueue cart fragments on all pages for custom mini-cart markup
add_action('wp_enqueue_scripts', function () {
    if (function_exists('is_woocommerce')) {
        wp_enqueue_script('wc-cart-fragments');
    }
});

You know it worked when the page source includes the cart-fragments script tag and the mini-cart count updates after adding a product without a full page reload.

Payment gateway misconfiguration

If the payment gateway does not appear on the checkout page at all (rather than appearing but failing), the cause is usually Checkout Block incompatibility, SSL enforcement, or geographic restrictions. The WooCommerce payment gateway not showing guide covers those cases in detail.

A less common but easy-to-miss cause: the payment gateway is in sandbox or test mode and the sandbox credentials have expired, or the gateway is set to live mode but the API keys belong to a different account or domain.

The symptom: checkout appears to work, the spinner runs, and the request completes, but the order is never created or is immediately set to "Failed" with a gateway error in the order notes.

How to check

  1. Go to WooCommerce > Settings > Payments and open the active gateway.
  2. Confirm whether the gateway is in live or test/sandbox mode.
  3. If sandbox mode is on, verify the sandbox credentials are current and for the correct store URL.
  4. Place a test order using the gateway's documented test card numbers (for Stripe: 4242 4242 4242 4242; for PayPal sandbox: a sandbox buyer account).
  5. Check WooCommerce > Orders for the new order. Open it and read the order notes for gateway-specific error messages.

If the order notes contain an authentication error, a "merchant not found" error, or a domain mismatch, the gateway credentials need to be replaced with valid ones for the current site URL and mode.

Session cookies blocked by a browser, WAF, or CDN

WooCommerce stores cart data server-side in the wp_woocommerce_sessions database table and links the browser to that session using the wp_woocommerce_session_* cookie. If anything between the server and the browser strips or blocks this cookie, every page load starts a new session. The cart empties itself on every navigation.

Things that block WooCommerce session cookies:

  • A CDN configured to strip all cookies from responses (common on Cloudflare Free plans with aggressive optimization).
  • A Web Application Firewall (WAF) rule that blocks cookies with long names or cookies matching a wildcard pattern.
  • Browser privacy extensions (uBlock Origin in strict mode, Privacy Badger) that block third-party or session cookies on all sites.
  • A cookie consent banner that blocks all cookies until consent is given, including WooCommerce's functional cookies.

How to check

Open the browser's Application tab (F12 > Application > Cookies) on the cart page after adding a product. Look for woocommerce_items_in_cart, woocommerce_cart_hash, and a cookie starting with wp_woocommerce_session_. If any of these are missing, the session is not persisting.

How to fix it

The fix depends on what is blocking the cookie. For a CDN, add the three WooCommerce cookie names to the CDN's cookie pass-through list. For a cookie consent tool, classify WooCommerce's session cookies as "functional" or "strictly necessary" so they are set before the visitor interacts with the consent banner. For a WAF, whitelist the cookie name patterns.

When to escalate

If none of the causes above match, collect the following before contacting your hosting provider or a developer:

  • The exact WordPress version, WooCommerce version, and PHP version (visible in WooCommerce > Status > System status).
  • A full copy of the system status report (WooCommerce > Status > Get system report).
  • A screenshot of the browser console (F12 > Console) showing any errors during the failing action.
  • A screenshot of the Network tab (F12 > Network) filtered to wc-ajax, showing the request URL, HTTP status, and response body.
  • The list of active plugins at the time of the failure.
  • Whether the problem started after a specific update (WordPress, WooCommerce, a plugin, a server change).
  • Whether the problem affects all customers or only specific browsers or devices.

This information lets a developer reproduce the problem instead of guessing. Without it, the first response will be "try disabling plugins", which is the slowest possible diagnostic path.

Preventing recurrence

  • Never add a server-level cache layer without explicitly excluding WooCommerce's dynamic pages and the ?wc-ajax= endpoint. The WooCommerce caching configuration guide lists every URL and cookie that must be excluded.
  • After any WooCommerce major version update, test the full purchase flow on staging: add to cart, view cart, proceed to checkout, complete payment with a test card, verify the order appears in WooCommerce > Orders.
  • After changing SSL configuration or moving behind a reverse proxy, verify that the ?wc-ajax= requests in the Network tab use https:// and return JSON, not HTML or a redirect.
  • If you use a custom mini-cart (not the WooCommerce Mini Cart widget), pin the wc-cart-fragments enqueue in your theme so a WooCommerce update does not silently break it.
  • If the checkout shows "No shipping options were found," the cause is usually shipping zone ordering or unmet free shipping conditions — see the WooCommerce shipping zones troubleshooting guide.
  • WooCommerce is a fundamentally different workload than a content site. Treat the store's conversion path as untouchable by any cache layer, and test it after every infrastructure change.

Want this to stop being your problem?

If outages or errors keep repeating, the fix is often consistency: updates, backups and monitoring that don't get skipped.

Compare maintenance plans

Search this site

Start typing to search, or browse the knowledge base and blog.