WooCommerce Cart Empties After Refresh: Sessions and Cache Fixes That Actually Work

Was this helpful?

Nothing tanks conversion like a cart that forgets it exists. A shopper adds two items, hits refresh, and—poof—empty cart. They don’t troubleshoot; they leave. Meanwhile you’re staring at dashboards that insist the site is “healthy” because HTTP 200 is not the same thing as “people can pay you money.”

This failure is almost never “WooCommerce is broken.” It’s usually state getting eaten by caching, cookie scope, session storage, or multi-node routing. The good news: you can fix it. The bad news: you have to be precise, because “clear all caches” is not a strategy; it’s a confession.

Fast diagnosis playbook

If you do this in the right order, you’ll find the bottleneck fast. If you do it in the wrong order, you’ll spend a day blaming WooCommerce and a night blaming yourself.

First: confirm whether it’s cookie/session loss or cached HTML

  • Open an incognito window, add a product to cart, refresh cart page and checkout page.
  • Check response headers for cache hits (CDN, Varnish, Nginx FastCGI cache).
  • Look for new cookies after add-to-cart: woocommerce_cart_hash, woocommerce_items_in_cart, wp_woocommerce_session_*.

Decision: if the HTML is cached, you need cache exclusions and correct “vary” behavior. If cookies aren’t persisting, you need cookie scope, HTTPS, SameSite, domain, or session storage fixes.

Second: check multi-node routing and session backend health

  • Are you behind a load balancer? Is there stickiness? Is WooCommerce session storage shared (DB/Redis) across nodes?
  • Are you using Redis object cache? Is it dropping keys? Is maxmemory evicting?
  • Is database latency spiking or tables locked?

Decision: if you have multiple web nodes and no shared session/object storage (or misconfigured), the cart will “randomly” disappear depending on which node you hit.

Third: validate WooCommerce session table and cron behavior

  • WooCommerce stores session data (by default) in the database in a dedicated table.
  • Cleanup jobs and TTL matter. Mis-tuned cleanup can delete live sessions.

Decision: if sessions are being purged or never written, you’ll see the cart vanish reliably on refresh or after a short idle.

One quote to keep the team honest (paraphrased idea): Eugene Rochal (operations/reliability circles) popularized the idea that reliability comes from “making state explicit and observable,” not from hope.

What’s actually happening when the cart “empties”

WooCommerce’s cart is not magic. It’s state built from a few ingredients:

  • Client-side cookies that identify the session and carry hints (like cart hash).
  • Server-side session data (often in wp_woocommerce_sessions or a custom table prefix) keyed by the session identifier.
  • HTML rendering that must not be cached incorrectly, because it’s user-specific.

When you add an item, WooCommerce writes or updates session data, sets cookies, and then renders the cart/mini-cart fragments. The “cart empties on refresh” symptom usually means one of these is true:

  • The browser never sends back the session cookie (cookie scope/flags are wrong).
  • The server can’t find the session data (not written, purged, or stored somewhere else).
  • A cache returns someone else’s version of the page (or a “no cart” version) because it ignored cookies or query parameters.
  • A different web node handles the next request and doesn’t share state properly.

There are variants:

  • Cart empties only on cart/checkout pages: page caching rules targeting those URLs, or a plugin that “optimizes” those paths.
  • Cart empties only after login: session migration between guest and logged-in user, cookie domain mismatch, or cache that varies on login cookies but not WooCommerce cookies.
  • Cart empties only on Safari/iOS: stricter cookie policies (SameSite, ITP), third-party cookie restrictions, or weird redirect flows.

Short joke #1: If your cache is serving the same cart to everyone, congrats—you’ve invented a “communal shopping” experience. It’s not the kind people pay for.

Interesting facts and context (the stuff that explains why this keeps happening)

  1. WooCommerce moved away from PHP sessions years ago for cart state; it relies on its own session system plus cookies, because PHP session scaling is a classic foot-gun in multi-node setups.
  2. WordPress page caching became mainstream because PHP+MySQL on shared hosting was slow. E-commerce is the first workload to punish the “cache everything” reflex.
  3. Varnish popularized “cache hits are king” thinking in the 2000s. For carts, “hit rate” is a vanity metric unless you exclude personalized pages correctly.
  4. Browsers tightened cookie rules in waves (SameSite defaults, tracking prevention). Many cart bugs are actually “your cookie flags are from 2016.”
  5. CDNs started caching HTML aggressively once edge compute made it attractive. That’s great for blogs and terrible for checkout unless you treat cookies as part of cache key.
  6. Redis became the default performance tool in WordPress land. Misconfigured eviction policies can delete object cache entries that code quietly assumes exist.
  7. WooCommerce uses “cart fragments” AJAX updates so the mini-cart updates without a full refresh. If those AJAX endpoints are cached or blocked, the UI lies.
  8. Many “security” plugins rewrite headers (especially cookie flags and cache control). Half of them are fine; the other half are chaos with a settings page.
  9. Load balancers made horizontal scaling easy. They also made state consistency your problem, which is why carts “randomly” vanish only after you add the second web node.

Failure modes: sessions, cookies, cache, and storage

1) Cookies: domain, path, HTTPS, SameSite, and the redirect trap

If the cart empties immediately after refresh, your first suspect is the session cookie not coming back. Common causes:

  • Mixed HTTP/HTTPS: add-to-cart happens on HTTPS, then a redirect or canonicalization sends the user to HTTP, dropping secure cookies.
  • Wrong cookie domain: example.com vs www.example.com. One sets cookies the other doesn’t send.
  • Cookie path too strict: cookie set for /shop but cart is /cart.
  • SameSite issues: payment provider redirects or embedded flows can behave differently when cookies aren’t marked correctly.
  • Headers already sent: PHP warnings or output can prevent setting cookies reliably. Yes, that one old plugin can still ruin your day.

2) Page caching: serving stale “empty cart” HTML

Page caching is great until it’s not. WooCommerce pages must be treated as personalized when the user has a cart session. The typical failures:

  • Cart and checkout URLs cached (CDN, Varnish, Nginx FastCGI cache, plugin cache).
  • Cache varies on WordPress login cookie but not WooCommerce session cookie. Guests with carts get served the “no cart” version.
  • Cart fragments endpoint cached (?wc-ajax=get_refreshed_fragments), resulting in UI showing empty mini-cart even though server state exists.
  • “Optimize for mobile” caches separately but doesn’t include cookies in cache key, splitting behavior across devices.

3) Object caching: Redis/Memcached assumptions and eviction

Object cache usually doesn’t store the cart itself, but it does store things WooCommerce depends on: customer data, product data, shipping zones, fragments, transients, and sometimes session-related helpers. Problems you’ll see:

  • Redis eviction under memory pressure: keys disappear, behavior becomes inconsistent, and the cart “sometimes” resets.
  • Non-persistent object cache across nodes: each node has its own local object cache; behavior differs depending on which node you hit.
  • Bad drop-in (object-cache.php) that doesn’t support groups properly or is incompatible with your WooCommerce version.

4) Session storage: database table issues, cleanup, and replication lag

WooCommerce’s session table is usually in MySQL/MariaDB. It’s fine—until it’s slow, locked, or on a replica that lags. Failure modes:

  • Read/write split with replication lag: add-to-cart writes to primary, refresh reads from replica that hasn’t caught up, so session appears empty.
  • Over-aggressive cleanup: sessions deleted too early because of TTL settings or a scheduled job misfire.
  • Table corruption or missing indexes: session lookups become slow; timeouts can cause WooCommerce to behave as if there’s no session.

5) Load balancers and multi-node WordPress: “works on one node” syndrome

Classic: you add a second web server and suddenly carts vanish. That’s not WooCommerce being fickle. That’s you running a stateful application like it’s a static website.

  • No sticky sessions plus local-only storage for something that should be shared.
  • Different salts/keys across nodes, breaking cookie validation and causing sessions to be rejected.
  • Different plugin versions across nodes (yes, it happens) leading to inconsistent cookie/session behavior.

6) Storage and filesystem: sessions aren’t your only write path

While WooCommerce sessions are DB-backed by default, your environment still writes to disk: logs, cache files, sometimes plugin caches. A full disk or read-only filesystem can create “cart empties” symptoms indirectly (e.g., plugins failing silently, database failing to write, PHP unable to create temp files).

Practical tasks: commands, outputs, and decisions (12+)

These are the tasks I actually run in production. Each has: command, typical output, what it means, and what decision you make.

Task 1: Confirm cache behavior on cart page (headers)

cr0x@server:~$ curl -I https://shop.example.com/cart/
HTTP/2 200
date: Sat, 27 Dec 2025 10:22:11 GMT
content-type: text/html; charset=UTF-8
cache-control: public, max-age=3600
age: 842
x-cache: HIT
via: 1.1 varnish

What it means: Your cart HTML is being cached publicly and served from cache. That’s a direct cause of “empty cart after refresh” for some or all users.

Decision: Disable caching for /cart, /checkout, and /my-account at every layer (plugin cache, reverse proxy, CDN). Also ensure cache varies on WooCommerce cookies for pages that must be dynamic.

Task 2: Check if WooCommerce cookies are set after add-to-cart

cr0x@server:~$ curl -I -c /tmp/cookies.txt -b /tmp/cookies.txt "https://shop.example.com/?add-to-cart=123"
HTTP/2 302
date: Sat, 27 Dec 2025 10:23:04 GMT
set-cookie: wp_woocommerce_session_9c1b2a3d4e5f6g7h=1%7C%7C1735300000%7C%7C1735296400%7C%7C0f...; expires=Mon, 29 Dec 2025 10:23:04 GMT; Max-Age=172800; path=/; secure; HttpOnly; SameSite=Lax
set-cookie: woocommerce_items_in_cart=1; path=/; secure; SameSite=Lax
set-cookie: woocommerce_cart_hash=8c4f...; path=/; secure; SameSite=Lax
location: https://shop.example.com/cart/

What it means: Cookies are being set with path=/ and secure. That’s good. If you don’t see these, the cart will not persist.

Decision: If cookies are missing, inspect PHP warnings/output, cookie domain configuration, and HTTPS redirects. If cookies exist but don’t come back, you have a browser/cookie policy problem or domain mismatch.

Task 3: Verify cookie domain mismatch via response cookies

cr0x@server:~$ curl -I https://www.shop.example.com/cart/
HTTP/2 200
date: Sat, 27 Dec 2025 10:24:01 GMT
set-cookie: wp_woocommerce_session_9c1b2a3d4e5f6g7h=...; path=/; secure; HttpOnly; SameSite=Lax

What it means: If your users land on both shop.example.com and www.shop.example.com, you’re likely setting separate cookies for each host. Users will “lose” carts during redirects or when navigating across hostnames.

Decision: Pick one canonical host, redirect the other, and make WordPress home/siteurl match. Avoid “sometimes www” setups.

Task 4: Detect FastCGI cache on Nginx for cart/checkout

cr0x@server:~$ curl -I https://shop.example.com/checkout/ | egrep -i "x-cache|x-fastcgi-cache|cache-control|set-cookie"
cache-control: max-age=600
x-fastcgi-cache: HIT

What it means: Nginx is serving cached checkout HTML. That can flatten carts into “empty” or cause one user to see another user’s state (which is worse).

Decision: Exclude WooCommerce endpoints from FastCGI cache and ensure the cache key varies on WooCommerce session cookie where appropriate.

Task 5: Check WooCommerce session table exists and has data

cr0x@server:~$ mysql -e "SHOW TABLES LIKE '%woocommerce_sessions%';"
+--------------------------+
| Tables_in_wp (%sessions%)|
+--------------------------+
| wp_woocommerce_sessions  |
+--------------------------+

What it means: Table exists. Next step is verifying writes and expiry behavior.

Decision: If it doesn’t exist, you have a broken install/migration, or a plugin changed session handler. Fix that before touching caching.

Task 6: Confirm sessions are being written and not instantly expired

cr0x@server:~$ mysql -e "SELECT session_key, session_expiry FROM wp_woocommerce_sessions ORDER BY session_expiry DESC LIMIT 3;"
+----------------------------------+---------------+
| session_key                      | session_expiry |
+----------------------------------+---------------+
| 9c1b2a3d4e5f6g7h                 |    1735300024 |
| 4aa02c0d1131c9b2                 |    1735299980 |
| 1fbb9d3d8a2a4e1a                 |    1735299901 |
+----------------------------------+---------------+

What it means: Expiry timestamps look reasonable (in the future). If you see expiries in the past or only a tiny window, something is purging sessions too aggressively.

Decision: If expiry is wrong, review WooCommerce session settings, cron, and any custom cleanup job. Also check server clock drift.

Task 7: Check database read/write split (replica lag)

cr0x@server:~$ mysql -e "SHOW SLAVE STATUS\G" | egrep "Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 18

What it means: Replica is 18 seconds behind. If your reads for session data go to the replica, a refresh right after add-to-cart can return “no session,” i.e., empty cart.

Decision: Don’t read session/cart-critical data from replicas. Pin WooCommerce session reads/writes to primary or ensure read-your-writes consistency.

Task 8: Verify Redis is evicting keys (object cache instability)

cr0x@server:~$ redis-cli INFO memory | egrep "used_memory_human|maxmemory_human|mem_fragmentation_ratio"
used_memory_human:1.92G
maxmemory_human:2.00G
mem_fragmentation_ratio:1.64

What it means: Redis is running near maxmemory with high fragmentation. If eviction is enabled, keys may drop under pressure, causing erratic behavior.

Decision: Increase Redis memory, tune eviction policy, reduce cache footprint, or move heavy transient usage elsewhere. Don’t run e-commerce state adjacent to a starving cache.

Task 9: Confirm Redis eviction policy and eviction counters

cr0x@server:~$ redis-cli CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "allkeys-lru"
cr0x@server:~$ redis-cli INFO stats | egrep "evicted_keys|keyspace_hits|keyspace_misses"
evicted_keys:48219
keyspace_hits:18022411
keyspace_misses:2401120

What it means: evicted_keys is non-zero and growing, which means Redis is deleting keys to make room. If plugins or themes assume cached objects exist, you get inconsistent UX.

Decision: Stop eviction in production caches only if you can tolerate writes failing; usually better is sizing properly and making sure cart-critical state is not stored only in Redis.

Task 10: Check that all web nodes share the same WordPress salts

cr0x@server:~$ ssh web1 "grep -E \"AUTH_KEY|SECURE_AUTH_KEY|LOGGED_IN_KEY|NONCE_KEY\" -n /var/www/html/wp-config.php | sha256sum"
7f6b0a0b8b2a1b0c44d5d5f2d0a...  -
cr0x@server:~$ ssh web2 "grep -E \"AUTH_KEY|SECURE_AUTH_KEY|LOGGED_IN_KEY|NONCE_KEY\" -n /var/www/html/wp-config.php | sha256sum"
2f1c7f0fd9d1c8e0ad0b3f1d9c...  -

What it means: Hashes differ. That’s bad. Cookie validation and auth/session-related behavior can differ per node.

Decision: Make wp-config.php consistent across nodes (ideally via config management). Restart PHP-FPM if needed. Then re-test carts.

Task 11: Check load balancer stickiness and backend switching

cr0x@server:~$ for i in {1..6}; do curl -sI https://shop.example.com/cart/ | awk -F': ' 'tolower($1)=="x-backend"{print $2}'; done
web2
web1
web2
web1
web2
web1

What it means: Requests alternate between nodes. If session storage is not shared or consistent, carts may vanish on refresh depending on which backend serves the request.

Decision: Either implement stickiness (short-term band-aid) or fix shared state (the correct long-term answer). For WooCommerce, shared DB session table is usually fine if reads/writes are consistent and latency is controlled.

Task 12: Verify PHP-FPM is not dropping requests or timing out on session writes

cr0x@server:~$ sudo tail -n 60 /var/log/php8.2-fpm.log
[27-Dec-2025 10:21:58] WARNING: [pool www] server reached pm.max_children setting (20), consider raising it
[27-Dec-2025 10:22:03] WARNING: [pool www] child 1934, script '/var/www/html/index.php' (request: "POST /?wc-ajax=add_to_cart") execution timed out (120.000 sec)

What it means: You’re saturating PHP-FPM workers and timing out during add-to-cart AJAX. If the write fails, the next refresh might legitimately show an empty cart.

Decision: Fix capacity: raise pm.max_children if CPU/RAM allows, optimize slow queries, and stop background plugins from hogging workers.

Task 13: Check Nginx access log for cart fragment caching or anomalies

cr0x@server:~$ sudo grep -E "wc-ajax=get_refreshed_fragments|wc-ajax=add_to_cart" /var/log/nginx/access.log | tail -n 5
203.0.113.10 - - [27/Dec/2025:10:23:05 +0000] "POST /?wc-ajax=add_to_cart HTTP/2.0" 200 1324 "-" "Mozilla/5.0"
203.0.113.10 - - [27/Dec/2025:10:23:06 +0000] "GET /?wc-ajax=get_refreshed_fragments HTTP/2.0" 200 4012 "-" "Mozilla/5.0"

What it means: The normal flow is happening. If you see 301/302 loops, 403s (WAF), or suspicious caching headers on these endpoints, the mini-cart can lie.

Decision: Exclude these AJAX endpoints from caching and ensure your WAF doesn’t block them. They’re not optional for a sane UX.

Task 14: Validate disk space and read-only filesystem (the boring sabotage)

cr0x@server:~$ df -h /var /tmp
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2  100G   99G  1.0G  99% /
tmpfs            16G   16G     0 100% /tmp

What it means: You’re out of space. Temp space full can break uploads, caching plugins, image processing, and sometimes session-related operations depending on stack.

Decision: Free space now. Then implement monitoring and sane log rotation. Don’t let “no space left on device” cosplay as a WooCommerce bug.

Task 15: Check WooCommerce-specific no-cache headers (should exist on sensitive pages)

cr0x@server:~$ curl -I https://shop.example.com/checkout/ | egrep -i "cache-control|pragma|expires"
cache-control: no-store, no-cache, must-revalidate, max-age=0
pragma: no-cache
expires: Wed, 11 Jan 1984 05:00:00 GMT

What it means: These headers are healthy. If you see public or long max-age, your caching layer may ignore WooCommerce’s intent or override headers.

Decision: Make caching layers respect origin headers on cart/checkout, or enforce bypass rules by path and by cookies.

Three corporate mini-stories from the trenches

Mini-story 1: The incident caused by a wrong assumption (the “replicas are fine for reads” story)

They had a clean architecture diagram and a messy reality. The stack was WordPress + WooCommerce behind a load balancer, database split into a primary for writes and a replica for reads. It worked for blog pages and product pages. The graphs looked great. Everyone high-fived the “scale” win.

Then the cart started emptying. Not always. Just often enough to crater revenue and create support tickets with the same tone as airline luggage complaints. The on-call engineer did what on-call engineers do: restarted PHP-FPM, purged caches, and stared at logs until coffee became a coping mechanism.

The root cause was a wrong assumption: “session reads are just reads.” WooCommerce wrote session data on add-to-cart to the primary, then the cart page read from the replica because the query looked like a read. With replication lag under load, the session record wasn’t there yet. The site behaved like it had amnesia, but only for the first few seconds after a change.

The fix was annoyingly simple and deeply political: stop routing WooCommerce session table reads to the replica. They implemented a rule to pin those queries to primary. Conversion recovered immediately. Nobody wanted to talk about it at the next architecture review, because it made the diagram less elegant.

Lesson: read-your-writes consistency beats “clean separation” when customers are clicking “Place order.”

Mini-story 2: The optimization that backfired (the “cache everything” campaign)

A performance initiative rolled through the company like a well-meaning storm. The directive was clear: increase cache hit rate. Someone added aggressive caching rules at the reverse proxy and a CDN setting to cache HTML for “dynamic pages” with a short TTL. It made product pages fast. It also made the cart page fast—fast at being wrong.

Customers reported that their carts emptied on refresh. Others reported seeing someone else’s cart count in the header. That second symptom is the one that turns a bug into a security incident meeting. The root cause: cache key didn’t vary on the WooCommerce session cookie. The CDN saw /cart/ as the same page for everyone.

The team tried “purge everything” every hour. They tried lowering TTL to 10 seconds. They tried adding a query string. All of that was treating a cache poisoning design mistake like it was an expiration problem. It wasn’t. They were caching personalized HTML without a personalization key.

The real fix was unglamorous: bypass caching for cart/checkout/account pages, and bypass caching when WooCommerce cookies are present. For everything else, cache normally. Hit rate dropped. Revenue rose. Which metric do you want to explain to the CFO?

Mini-story 3: The boring but correct practice that saved the day (config consistency)

Another org had three web nodes and a habit of “hot fixes.” One engineer would SSH into a node, tweak wp-config.php, and call it a day. The next engineer did the same on a different node. Over time, the nodes drifted like continents: different salts, different plugin files, slightly different PHP INI settings.

Cart loss appeared “random.” It wasn’t random; it was deterministic chaos. Users would add to cart, refresh, hit a different backend, and the session cookie would fail validation. The system did what it was told: reject unknown signatures and start a new session. Hello empty cart.

The fix was so boring it almost didn’t happen: they stopped treating production servers like pets. They put config under version control, deployed the same wp-config.php across nodes, and added a startup check that verified salt hashes matched. They also pinned plugin deployments to a single artifact.

Nothing “innovative” came out of it. But the incident rate dropped. On-call got quieter. And the cart stopped acting like a goldfish.

Short joke #2: If your web nodes have different salts, your cluster isn’t “high availability.” It’s “choose your own adventure,” except the ending is always support tickets.

Common mistakes: symptom → root cause → fix

This is the part where you stop guessing.

1) Symptom: Cart empties immediately on refresh

Root cause: Session cookie not being sent back (domain/HTTPS mismatch) or session not written.

Fix: Enforce one canonical hostname, force HTTPS, verify cookies include path=/, and check for PHP warnings that prevent Set-Cookie. Validate wp_woocommerce_session_* presence.

2) Symptom: Cart empties only on cart/checkout pages

Root cause: Those pages are cached (CDN/Varnish/FastCGI/plugin cache).

Fix: Bypass caching for /cart, /checkout, /my-account, and WooCommerce AJAX endpoints. Confirm origin sends no-store and caches respect it.

3) Symptom: Cart empties “randomly” in a multi-server setup

Root cause: Node inconsistency (different salts/config), non-shared storage, or load balancer switching without shared session consistency.

Fix: Make configuration identical across nodes (salts, plugins, WP versions). Ensure WooCommerce session storage is shared and reads are consistent. Use stickiness only as a temporary mitigation.

4) Symptom: Cart empties after login

Root cause: Session migration edge cases plus caching that varies on login cookies but not WooCommerce cookies, or cookie domain mismatch.

Fix: Ensure cache bypass when WooCommerce session cookies exist. Confirm canonical host before login. Test login flow with and without items in cart.

5) Symptom: Mini-cart shows empty, but cart page shows items

Root cause: Cart fragments AJAX endpoint is cached or blocked; or JS optimization breaks fragment refresh.

Fix: Exclude wc-ajax=get_refreshed_fragments from caching and WAF rules. Verify browser console errors and network responses.

6) Symptom: Cart works, then empties after a few minutes

Root cause: Session expiry/cleanup too aggressive, server time skew, or Redis eviction affecting supporting state.

Fix: Validate session expiry timestamps, cron cleanup behavior, and NTP/time sync. Check Redis evicted_keys.

7) Symptom: Cart empties only for some geographies

Root cause: CDN edge caching rules differ by region; or geo-specific redirects change hostname/protocol.

Fix: Standardize redirect rules globally. Ensure cache key includes relevant cookies or bypass for personalized pages everywhere, not just “primary” POPs.

8) Symptom: Cart empties under load

Root cause: Timeouts on add-to-cart requests, DB contention on session table, or PHP-FPM saturation.

Fix: Tune PHP-FPM, optimize DB (indexes, slow query fixes), and reduce plugin overhead. Monitor timeouts and 5xx rates specifically for WooCommerce AJAX endpoints.

Checklists / step-by-step plan

Phase 1: Reproduce and isolate (15–30 minutes)

  1. Reproduce in incognito on desktop and mobile. Note exact page where cart disappears.
  2. Test canonical host: always use the same hostname and HTTPS.
  3. Check headers on /cart and /checkout for cache hits and wrong cache-control.
  4. Check cookies after add-to-cart: confirm wp_woocommerce_session_* is set and returned.

Phase 2: Kill the wrong caches (without nuking performance)

  1. Disable page caching for:
    • /cart/
    • /checkout/
    • /my-account/
    • /?wc-ajax=*
  2. Bypass cache when WooCommerce cookies exist:
    • woocommerce_items_in_cart
    • woocommerce_cart_hash
    • wp_woocommerce_session_*
  3. Confirm with curl that Age and cache hit headers disappear on cart/checkout.

Phase 3: Make sessions reliable across nodes

  1. Ensure identical salts and config on all nodes.
  2. Check load balancer behavior: if requests round-robin, confirm session storage is shared and consistent.
  3. Remove replica reads for WooCommerce sessions and cart-critical queries if you have replication lag.

Phase 4: Make storage boring (the best kind of storage)

  1. Verify DB health: session table exists, indexed, and not locking under load.
  2. Verify Redis health if used: no eviction storms, adequate memory, sane policy.
  3. Verify disk space and file permissions; eliminate intermittent write failures.

Phase 5: Regression tests you should keep

  • Add-to-cart → refresh cart page → still has items.
  • Add-to-cart → login → cart preserved.
  • Add-to-cart → switch between www and non-www should never happen; verify it doesn’t.
  • Add-to-cart → checkout page shows items and totals; mini-cart matches.
  • Repeat with CDN enabled and disabled to ensure rules are correct at the edge.

FAQ

1) Why does the cart empty only after a refresh, not immediately?

Because add-to-cart may succeed (cookies set, session written), but the refresh is served by a different cache layer or backend node that doesn’t see that state. Refresh is where caching and routing mistakes show up.

2) Should I just disable all caching for WooCommerce?

No. Cache product/category pages aggressively, but treat cart/checkout/account as personalized. Blanket disabling is leaving money on the table and punishing your origin servers for no reason.

3) Is this usually caused by a plugin?

Often, yes—but not “WooCommerce is buggy.” The common culprits are cache plugins, security plugins that rewrite headers, and “optimization” plugins that minify/defer scripts and break cart fragments.

4) Do I need sticky sessions on the load balancer?

Sticky sessions can mask issues, but they’re not the ideal fix. The better answer is shared, consistent session storage and identical configuration across nodes. Use stickiness as a short-term mitigation during an incident.

5) Can Redis cause cart loss?

Indirectly. If you store critical session-like state in Redis or rely on cached helpers and Redis is evicting keys under memory pressure, behavior becomes inconsistent. Fix Redis sizing and eviction, and ensure cart-critical state isn’t “cache-only.”

6) Why does it happen more on mobile or Safari?

Cookie rules are stricter and tracking prevention is more aggressive. If you have cross-domain redirects, mixed protocol, or odd SameSite settings, mobile browsers will punish you first.

7) How do I know if the CDN is caching my cart page?

Look at headers: Age, CDN cache status headers, and any HIT indicators. Also compare HTML response bodies between two different clients; if they match suspiciously well, you’re caching personalized content.

8) Could database performance cause the cart to “empty”?

Yes. If session writes time out or reads are slow enough to fail, WooCommerce can fall back to a new session. Under load, this looks like “cart randomly empties.” Check PHP-FPM timeouts and DB slow logs.

9) My cart empties only after login. What’s the fastest fix?

First, ensure you’re not flipping hostnames/protocol during login. Second, bypass caches when WooCommerce session cookies are present. Third, confirm salts match across nodes so login cookies validate consistently.

10) What’s the single most common root cause?

Improper caching of cart/checkout pages or failure to vary cache by WooCommerce cookies. It’s the easiest performance “win” to implement and the fastest way to break revenue.

Conclusion: next steps that won’t waste your weekend

Fixing “cart empties after refresh” is mostly about being disciplined with state. Start with the fast diagnosis: confirm whether you’re losing cookies/sessions or serving cached HTML. Then remove caching from the few endpoints that must be dynamic. After that, make multi-node behavior boring: identical config, shared state, and no replica lag surprises.

Practical next steps:

  1. Run header checks on /cart and /checkout and eliminate cache hits there.
  2. Verify WooCommerce cookies are set and returned on the canonical HTTPS hostname.
  3. Audit load balancer behavior and config drift across nodes (especially salts).
  4. Validate WooCommerce session table writes and ensure session reads aren’t going to lagging replicas.
  5. Check Redis eviction and PHP-FPM timeouts if the issue correlates with load.

Do those five, and you’ll usually go from “mysterious cart amnesia” to a stable checkout. Which is the whole point of running a store.

← Previous
Email backups: The restore drill you must run (or your backups are fake)
Next →
MariaDB vs Elasticsearch for Site Search: When the Search Cluster Is Mandatory

Leave a comment