Skip to content
ThemesIonic — home
WooCommerce

Fixing "Sorry, your session has expired" at WooCommerce Checkout

WooCommerce shows the expired-session notice when the checkout arrives with an empty cart, usually because the session cookie never reached the server or a cached page carried someone else's nonce.

5 min read intermediate

The notice means the checkout request reached the server with an empty cart. In WooCommerce's classic checkout, "Sorry, your session has expired. Return to shop" is thrown from WC_Checkout::process_checkout() in exactly two situations: the form's security nonce fails and the cart is empty, or the cart is empty when the order is about to be processed. Nothing in that code checks a clock. A shopper who waited an hour and a shopper who waited three seconds get the same message if their cart is not attached to the request.

So the job is to find out why the cart is missing. There are four common reasons, and caching is the one behind most reports.

Which message are you actually seeing?

WooCommerce has several close variants, and each points somewhere different:

Message Where it comes from What it means
Sorry, your session has expired. Return to shop Place order, classic checkout Nonce failed and cart empty, or cart empty
Sorry, your session has expired. (on the order review) Checkout AJAX refresh Nonce rejected twice, or cart empty
We were unable to process your order, please try again. Place order, classic checkout Nonce failed but cart still has items
Cannot place an order, your cart is empty. Block checkout (Store API) The block equivalent of an empty cart

The third row is the useful clue. If customers mostly get "unable to process", the cart is fine and the nonce is wrong. That almost always means a cached checkout page. If they get "session has expired", the cart itself is not reaching the server.

Since WooCommerce changed the order review refresh, a rejected nonce there first triggers one automatic page reload. The expired notice only appears if the reloaded page is rejected too, or if the browser has no session cookie, which would make the reload loop forever. A checkout that silently reloads once and then works was a stale page restored from the browser's back button. That is harmless.

Cause 1: the checkout or cart page is cached

For guests, WooCommerce ties checkout nonces to the session's customer ID through the nonce_user_logged_out filter, not to user ID 0. So a checkout page cached for one visitor carries a nonce that is only valid for that visitor's session. Serve it to someone else and the nonce check fails for everyone except the first visitor.

Check the response headers of the checkout page while logged out:

curl -sI https://example.com/checkout/ | grep -iE 'cache|age|x-cache|cf-cache-status'

A HIT, an Age greater than zero or a long max-age means the page is coming from a cache. Exclude cart, checkout and my-account at every layer: the caching plugin, the host's server cache and the CDN. Also bypass the cache for any request carrying the woocommerce_items_in_cart or wp_woocommerce_session_ cookies. Excluding pages from cache covers the plugin side. A CDN "cache everything" rule overrides all of it, which is the usual trap on Cloudflare.

The cart lives in the wp_woocommerce_sessions table, and the browser finds it through a cookie named wp_woocommerce_session_ followed by a hash of the site URL. If that cookie is not sent with the checkout request, WooCommerce starts a fresh, empty session. Typical ways to lose it:

  • Mixed hostnames. The shopper adds to cart on www.example.com and is sent to checkout on example.com, or the other way round. The cookie belongs to the first host only. Make the site address in Settings › General match every link and redirect.
  • A cached response that strips Set-Cookie. Some CDNs drop cookies from responses they cache. The add-to-cart request then never sets the session at all.
  • HTTPS detection behind a proxy. WooCommerce marks the cookie secure when the site URL is HTTPS and is_ssl() is true. If a load balancer terminates TLS and PHP does not see it, redirects and cookies can disagree about the scheme.
  • Browser or privacy tools blocking first-party cookies, which is the same mechanism as cookies are blocked on the login screen.

In the browser's developer tools, open Application › Cookies on the checkout page. If wp_woocommerce_session_… is missing after adding a product, the problem is here, not in the checkout code.

Cause 3: the session really expired

Guest sessions last two days and logged-in sessions seven, renewed while the shopper is active. Separately, WordPress nonces are valid for 12 to 24 hours. A checkout tab left open overnight therefore fails the nonce check. If the session has also been cleaned up, the cart is gone and the expired notice is accurate.

You can lengthen sessions:

add_filter( 'wc_session_expiration', function () {
	return 7 * DAY_IN_SECONDS; // guests too
} );

Do this only for a real buying cycle, such as a B2B store where orders are prepared over days. It grows the sessions table, and it does nothing for the caching and cookie causes above.

Cause 4: something emptied the cart

Custom code on woocommerce_before_checkout_process, or a plugin that empties the cart on login, logout or currency switch, leaves the cart empty right before WooCommerce checks it. The result looks exactly like an expired session. A quick test is to switch to a default theme and disable everything except WooCommerce and the payment gateway on a staging copy, as in checkout not working. Then retry with the same steps the customer took.

Common mistakes

  • Running Status › Tools › Clear customer sessions as a fix. It empties every shopper's cart and fixes none of the causes.
  • Excluding only /checkout/. The checkout endpoints order-received and order-pay sit under it, but the cart page and the ?wc-ajax= requests must not be cached either. See endpoints not working if those URLs behave oddly after you change permalinks.
  • Testing while logged in as admin. Most caches bypass logged-in users, so the bug never reproduces for you.

Verify

  1. In a private window, add a product, go to checkout and confirm the wp_woocommerce_session_ cookie is present.
  2. Confirm the checkout response has no cache hit header.
  3. Place a test order. Then open the checkout in a second private window and check that each window gets a different nonce in the woocommerce-process-checkout-nonce field.
  4. Repeat on the other hostname variant (www or bare) to check that redirects keep the shopper on one host.

Frequently asked

Two days for guests and seven days for logged-in customers, renewed once a day while the shopper is active. The wc_session_expiration and wc_session_expiring filters change it, and WooCommerce logs a warning if you push expiration past 30 days or expiring past 29.
Not necessarily. It means the request that reached the checkout had no cart behind it. Very often the cart still exists in the database, but the browser did not send the cookie that points to it, or a cached page handed over a stale form.
That is the same nonce check failing while the cart still has items. WooCommerce says the session has expired whenever the cart is empty, whether or not the security check failed, so the second message points at caching or nonces rather than a lost cart.

Related guides