When a WooCommerce checkout breaks, it doesn’t “sort of” break. It detonates revenue, support queues, and your confidence in reality. Customers see spinners, blank pages, “There was an error processing your order,” or the classic: nothing happens when they click Place order.
The trick is to stop guessing. Checkout is a chain: browser → theme/JS → WooCommerce AJAX → WordPress/PHP → database → payment gateway → webhook callbacks → email/stock updates. One weak link and the whole thing looks like “checkout not working.” Let’s find the link.
Fast diagnosis playbook
If you’re on-call right now, start here. This is the highest signal-to-noise sequence to find the bottleneck quickly.
1) Confirm it’s real and scoped
- Try an incognito checkout with a low-value product. Note the exact symptom: spinner, redirect, gateway error, blank page, or order created but payment failed.
- Try a second network (mobile hotspot). If it works there, suspect corporate DNS/WAF/caching weirdness on the first network.
- Check one other browser. If only Safari fails, you’re probably dealing with cross-site cookie rules, iframe payments, or aggressive tracking prevention.
2) Look at browser console + network before touching the server
- Open DevTools → Console for JS errors.
- DevTools → Network → filter for
?wc-ajax=checkout,admin-ajax.php, and gateway endpoints. Watch status codes and response body.
3) Check WooCommerce logs (they’re boring, which is why they work)
- WooCommerce → Status → Logs. Look for gateway logs, fatal errors, webhook failures.
- WordPress debug log and PHP-FPM error log if you control the server.
4) Decide which lane you’re in
Most checkout failures land in one of these lanes:
- SSL/HTTPS or cookies: redirect loops, mixed content, “nonce invalid,” infinite spinner.
- Gateway/API: Stripe/PayPal failing, webhooks not received, 3DS issues.
- Plugin/theme/caching: JS broken, AJAX blocked, cached checkout pages.
- Server limits: timeouts, memory errors, slow DB, disk full, CPU pegged.
5) Apply the safest quick mitigations
- Disable full-page cache for
/cart/,/checkout/,/my-account/, and all?wc-ajax=endpoints. - Temporarily switch to a default theme and disable non-essential plugins on a staging copy (or using a “disable on checkout only” approach if you must do it live).
- Roll back the last change: plugin update, theme update, CDN/WAF rule, PHP upgrade. Checkout breakages love fresh changes.
Checkout failure map: where it breaks and how it looks
Browser and JavaScript layer
Symptoms: Place order button does nothing, spinner forever, fields won’t validate, payment element doesn’t load.
Typical causes: JS minification gone wrong, mixed content blocks, theme overriding checkout templates, outdated gateway scripts, CSP rules, or plugin-injected scripts causing syntax errors.
WooCommerce AJAX / REST layer
Symptoms: 400 or 403 on ?wc-ajax=checkout, “nonce invalid,” session lost, cart empties on refresh.
Typical causes: caching, cookies blocked, server-side WAF rule, Cloudflare/edge caching, or misconfigured site URL / HTTPS forcing.
WordPress / PHP layer
Symptoms: HTTP 500, blank page, “critical error,” random failures under load.
Typical causes: PHP fatal errors, memory limit too low, opcode cache issues, incompatible plugin versions, or PHP-FPM worker exhaustion.
Database and storage layer
Symptoms: checkout works sometimes but gets slow; orders created with missing metadata; deadlocks; intermittent “database error.”
Typical causes: slow MySQL queries, saturated IO, full disk, missing indexes, or heavy background jobs contending with checkout writes.
Gateway and webhook layer
Symptoms: order stuck “pending payment,” payment captured but order not marked paid, customer charged but no order email, refunds failing.
Typical causes: webhook delivery failures, incorrect webhook secret, blocked inbound requests, API keys wrong environment, 3DS flows blocked, or server clock skew breaking signatures.
Interesting facts and short history (yes, it matters)
- WooCommerce started as WooThemes’ eCommerce plugin and was later acquired by Automattic; this is why WordPress conventions permeate everything, for better and worse.
- Checkout uses a mix of classic admin-ajax and WooCommerce AJAX endpoints; different security and caching assumptions apply depending on the route used.
- “Nonces” in WordPress aren’t cryptographic nonces; they’re time-window tokens tied to sessions. If caches or proxies replay pages, nonces rot.
- PCI compliance pressure is why many gateways moved card entry into hosted fields (iframes/elements). That makes modern checkouts sensitive to CSP, mixed content, and third-party cookie rules.
- 3-D Secure (3DS) flows became far more common after SCA rules in Europe; gateways now require extra browser steps that break easily when JS or redirects are interfered with.
- HSTS can make “it works on my machine” a lie; once a browser caches HSTS, it will force HTTPS even if you later break HTTPS.
- Many “checkout issues” are actually caching bugs: caching a personalized page is like photocopying someone’s passport and handing it to the next customer.
- Webhook retries are not infinite. If your server blocks gateway callbacks for hours, you’ll be manually reconciling payments later.
- Cloud WAFs increasingly default to blocking “suspicious” POSTs; checkout forms are basically a buffet of fields that can trip rules.
SSL and HTTPS: mixed content, HSTS, and redirect loops
How SSL breaks checkout specifically
SSL problems rarely show up as a friendly “SSL is broken” banner. Instead you get side effects:
- Payment widgets fail to load because a single script is served over HTTP (mixed content blocked).
- Cookies aren’t set because you’re bouncing between HTTP and HTTPS, or between
wwwand apex domains. - Nonce validation fails because cached content is served across schemes.
- Redirect loops because WordPress thinks the site URL is HTTP but the proxy terminates TLS, or vice versa.
Mixed content: the silent checkout killer
Modern browsers will block insecure resources on secure pages. You’ll see it in the browser console as “Mixed Content: The page was loaded over HTTPS, but requested an insecure resource.” When that insecure resource is your payment gateway JS or a checkout validation script, the checkout becomes interpretive dance.
Reverse proxies and “is_ssl()” lying to you
If you’re behind a load balancer or CDN that terminates TLS, the origin server may only see HTTP. WordPress then generates HTTP URLs, sets cookies incorrectly, and gates secure behavior on the wrong signal.
Fix is usually to ensure the proxy sets X-Forwarded-Proto: https, and WordPress is configured to trust it (often via the hosting stack). Don’t “fix” this by hardcoding redirects in five places. That’s how you get loops that only happen on Tuesdays.
Two precise rules
- Rule 1: checkout and account pages must be consistently HTTPS with one canonical hostname.
- Rule 2: never cache pages containing nonces, carts, sessions, or personalized fragments.
Payments: gateways, webhooks, 3DS, and “order pending forever”
The two halves of “payment succeeded”
A successful payment is usually two events:
- The customer completes the on-site flow (tokenization/authorization/capture).
- The gateway sends a server-to-server webhook confirming the final state.
If #1 works but #2 fails, you get the nightmare: customers are charged, but orders sit in “pending payment” or “on-hold.” That’s not a “gateway issue.” That’s an integration reliability issue, and it’s yours to own.
Stripe: common breakpoints
- Webhook secret mismatch: events arrive but fail signature validation.
- Wrong API keys: live site using test keys or vice versa; sometimes only some customers trigger failures due to payment method differences.
- 3DS modal blocked: aggressive CSP, broken JS, or interference from optimization plugins.
PayPal: common breakpoints
- Return URL problems: mismatched scheme/host results in failed return from PayPal or loop to cart.
- IPN/webhook blocked: inbound callbacks blocked by firewall, WAF, or misrouted DNS.
Joke #1: A payment gateway integration is like a friendship: it’s fine until someone stops calling you back, and then it’s all “pending.”
Clock skew: a surprisingly real checkout failure
Signed webhook payloads and API auth can fail when your server clock drifts. If NTP is broken, your order pipeline becomes quantum: paid and not paid at the same time.
Plugin and theme conflicts: the silent assassins
Why checkout is a conflict magnet
Checkout pages are where every plugin wants a moment on stage: analytics, pixel tracking, upsells, multi-currency, address validation, caching, security, cookie consent, “improve conversions,” “improve performance,” “improve happiness.” Most of them do it by injecting scripts and filtering output. It only takes one bad actor to break the whole runtime.
How to think about conflicts
- JS conflicts: one script throws an exception, the rest of the checkout JS stops executing.
- Template overrides: themes overriding WooCommerce checkout templates may be outdated relative to your WooCommerce version.
- Validation hooks: plugins add custom checkout fields but don’t handle edge cases, causing validation failures.
- Security plugins: block AJAX endpoints, REST requests, or POST bodies.
Stop “optimizing” checkout with minify/bundle roulette
Minification plugins that concatenate scripts can break gateway hosted fields and checkout fragments. If you can’t explain the dependency graph, don’t let a plugin “auto-optimize” it.
Caching, CDNs, and WAF rules that sabotage checkout
What must never be cached
/cart/,/checkout/,/my-account/- Any URL with
add-to-cart=,wc-ajax=, or session-bearing query parameters - Responses that set cookies like
woocommerce_cart_hash,woocommerce_items_in_cart,wp_woocommerce_session_*
CDN pitfalls
CDNs are great until they decide your checkout is “static.” Then customers share sessions like it’s a community garden. Also: WAF managed rules sometimes flag checkout posts as SQL injection because, well, addresses and names look like user input (because they are).
Security headers: good intentions, broken payments
Content Security Policy (CSP) and SameSite cookie settings can break third-party payment flows. You can absolutely use CSP; just don’t do it by guessing. Start with report-only, capture violations, then tighten.
Joke #2: The only thing more confident than a WAF is a marketing plugin that “guarantees” faster conversions.
Server-side failures: PHP, DB, disk, and timeouts
Checkout is a write-heavy request: it creates an order, updates stock, stores metadata, maybe talks to a gateway, maybe triggers tax/shipping APIs, and then fires emails. That’s a lot of moving parts inside one HTTP request.
PHP-FPM and worker exhaustion
When PHP-FPM runs out of workers, requests queue. Checkout times out, browsers retry, and you create duplicate orders. That’s how you wake up to “why do we have 200 pending orders?”
Database performance and locking
WooCommerce stores a lot of data in post tables and meta tables. Under load, poorly indexed queries and lock contention can make checkout slow and flaky. If you see spikes in DB CPU or slow query logs filled with meta queries, you don’t need “more cache.” You need to fix the database workload.
Disk full: the most embarrassing outage
If the disk fills, PHP can’t write sessions, logs, or temporary files; MySQL can’t flush; and WordPress starts failing in creative ways. Storage failures rarely announce themselves politely.
One reliability quote (paraphrased idea)
“Hope is not a strategy.” — paraphrased idea often attributed to seasoned operations leaders. In checkout land, you verify with logs and traces, or you’re just vibing.
Practical diagnostic tasks (commands + output + decisions)
These are real tasks you can run on a typical Linux host with Nginx/Apache, PHP-FPM, and MySQL/MariaDB. Use them to move from “checkout broken” to “this specific component is failing.” Each task includes: command, sample output, and the decision you make.
Task 1: Confirm DNS and TLS are sane from the server
cr0x@server:~$ dig +short A example-shop.com
203.0.113.10
cr0x@server:~$ echo | openssl s_client -servername example-shop.com -connect example-shop.com:443 2>/dev/null | openssl x509 -noout -subject -issuer -dates
subject=CN = example-shop.com
issuer=CN = R3, O = Let's Encrypt, C = US
notBefore=Nov 1 00:00:00 2025 GMT
notAfter=Jan 30 23:59:59 2026 GMT
What it means: DNS points where you expect and the certificate matches the hostname with valid dates.
Decision: If the CN/SAN doesn’t match, fix the cert/virtual host. If DNS points to a different IP than your origin, your CDN/load balancer config may be wrong.
Task 2: Detect redirect loops or HTTP→HTTPS confusion
cr0x@server:~$ curl -I -L -s -o /dev/null -w '%{url_effective} %{http_code} %{num_redirects}\n' http://example-shop.com/checkout/
https://example-shop.com/checkout/ 200 1
cr0x@server:~$ curl -I -s https://example-shop.com/checkout/ | egrep -i 'location:|set-cookie:|strict-transport-security'
strict-transport-security: max-age=31536000; includeSubDomains; preload
set-cookie: wp_woocommerce_session_123abc=...; path=/; secure; HttpOnly
What it means: One redirect to HTTPS is fine. The presence of Secure cookies on checkout is good.
Decision: If num_redirects is high or status is 301/302 looping, fix WordPress Site URL/Home URL and proxy headers before touching plugins.
Task 3: Find mixed content candidates in generated HTML
cr0x@server:~$ curl -s https://example-shop.com/checkout/ | egrep -o 'http://[^"]+' | head
http://example-shop.com/wp-content/plugins/some-plugin/js/tracker.js
http://fonts.example.net/somefont.woff2
What it means: The checkout HTML is referencing HTTP assets; browsers may block them.
Decision: Fix hardcoded HTTP URLs in theme/plugin settings, or correct WordPress URL settings and run a proper content replacement (carefully) if needed.
Task 4: Watch Nginx/Apache logs for checkout requests in real time
cr0x@server:~$ sudo tail -f /var/log/nginx/access.log | egrep 'checkout|wc-ajax=checkout|admin-ajax.php'
203.0.113.55 - - [27/Dec/2025:11:02:13 +0000] "POST /?wc-ajax=checkout HTTP/2.0" 400 182 "-" "Mozilla/5.0"
203.0.113.55 - - [27/Dec/2025:11:02:13 +0000] "GET /checkout/ HTTP/2.0" 200 98234 "-" "Mozilla/5.0"
What it means: The checkout AJAX endpoint is returning 400. That’s not “a theme issue” until proven otherwise.
Decision: Move to PHP/Woo logs to see the exact error (nonce, validation, gateway response).
Task 5: Inspect PHP-FPM health and worker pressure
cr0x@server:~$ sudo systemctl status php8.2-fpm --no-pager
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
Active: active (running) since Sat 2025-12-27 09:10:02 UTC; 1h 52min ago
cr0x@server:~$ sudo grep -i 'server reached pm.max_children' /var/log/php8.2-fpm.log | tail -3
[27-Dec-2025 10:59:01] WARNING: [pool www] server reached pm.max_children setting (20), consider raising it
What it means: PHP-FPM is saturating. Under load, checkout will queue or time out.
Decision: Increase pm.max_children only if CPU/RAM allow; otherwise reduce plugin load, add caching for non-checkout pages, and fix slow requests.
Task 6: Locate PHP fatal errors triggered during checkout
cr0x@server:~$ sudo tail -n 80 /var/log/php8.2-fpm.log
[27-Dec-2025 11:02:13] PHP Fatal error: Uncaught Error: Call to undefined function wc_get_order() in /var/www/html/wp-content/plugins/custom-checkout/hooks.php:41
Stack trace:
#0 /var/www/html/wp-includes/class-wp-hook.php(324): custom_checkout_validate()
What it means: A plugin is calling WooCommerce functions incorrectly or too early, causing a fatal during checkout.
Decision: Disable or patch the plugin. A fatal error is not a “maybe.” It’s a stop-the-line defect.
Task 7: Confirm WordPress can write sessions and disk isn’t full
cr0x@server:~$ df -h / /var
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 39G 0.9G 98% /
cr0x@server:~$ sudo du -sh /var/log | tail -1
6.2G /var/log
What it means: You’re nearly out of disk. Databases and PHP will fail in weird ways at 99–100%.
Decision: Free space immediately (rotate/compress logs, clear old backups), then put monitoring/alerts in place. Disk full is a policy failure, not a mystery.
Task 8: Check MySQL availability and current load
cr0x@server:~$ mysqladmin -uroot -p ping
mysqld is alive
cr0x@server:~$ mysql -uroot -p -e "SHOW GLOBAL STATUS LIKE 'Threads_running';"
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| Threads_running | 58 |
+-----------------+-------+
What it means: MySQL is up, but 58 running threads is often “we’re hurting” on a small instance.
Decision: Inspect slow queries and lock waits; consider scaling DB resources or optimizing queries/plugins generating heavy meta lookups.
Task 9: Identify slow queries during checkout window
cr0x@server:~$ sudo tail -n 20 /var/log/mysql/slow.log
# Time: 2025-12-27T11:02:13.123456Z
# Query_time: 3.812 Lock_time: 0.002 Rows_sent: 1 Rows_examined: 250000
SELECT post_id FROM wp_postmeta WHERE meta_key='_billing_email' AND meta_value='user@example.com';
What it means: A meta query is scanning too many rows. Checkout often triggers these lookups via plugins (CRM, fraud, account matching).
Decision: Reduce or remove the plugin feature, add appropriate indexes (carefully), or rework the query path. Throwing cache at write paths rarely ends well.
Task 10: Confirm webhook endpoint is reachable externally
cr0x@server:~$ curl -s -o /dev/null -w "%{http_code}\n" https://example-shop.com/?wc-api=wc_stripe
200
cr0x@server:~$ sudo tail -n 50 /var/log/nginx/access.log | egrep 'wc-api=wc_stripe|webhook'
198.51.100.22 - - [27/Dec/2025:11:01:44 +0000] "POST /?wc-api=wc_stripe HTTP/2.0" 200 42 "-" "Stripe/1.0 (+https://stripe.com/docs/webhooks)"
What it means: The endpoint responds and you can see incoming POSTs.
Decision: If you see no POSTs, the gateway isn’t reaching you (DNS/firewall/WAF). If you see 4xx/5xx, fix application handling or WAF rules.
Task 11: Catch WAF or security plugin blocking POST bodies
cr0x@server:~$ sudo grep -iE 'modsecurity|access denied|waf|forbidden' /var/log/nginx/error.log | tail -10
2025/12/27 11:02:13 [error] 1234#1234: *8890 ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Rx' with parameter `(?i:select.+from)'" against variable `ARGS:billing_address_1'
What it means: A WAF rule is falsely flagging checkout fields as SQLi.
Decision: Add a targeted exception for checkout endpoints, not a blanket disable. Keep security, but teach it manners.
Task 12: Validate WordPress site URLs and HTTPS settings via WP-CLI
cr0x@server:~$ cd /var/www/html
cr0x@server:~$ wp option get home
https://example-shop.com
cr0x@server:~$ wp option get siteurl
https://example-shop.com
What it means: WordPress believes it’s on HTTPS with the correct host.
Decision: If these are HTTP or the wrong domain, fix them. Mismatched URLs create redirect loops and cookie scope problems.
Task 13: Check active plugins quickly (and spot risky ones)
cr0x@server:~$ wp plugin list --status=active
+---------------------------+--------+-----------+---------+
| name | status | update | version |
+---------------------------+--------+-----------+---------+
| woocommerce | active | available | 9.3.2 |
| woocommerce-gateway-stripe| active | none | 8.7.0 |
| wp-rocket | active | none | 3.16.0 |
| wordfence | active | none | 7.11.0 |
| some-minify-plugin | active | none | 2.4.1 |
+---------------------------+--------+-----------+---------+
What it means: You can see caching/security/minify plugins that commonly interfere with checkout.
Decision: If checkout broke after an update, roll back that specific plugin first. If you must test, disable optimization/minify on checkout URLs.
Task 14: Verify cron isn’t choking order completion emails and stock updates
cr0x@server:~$ wp cron event list | head
+-------------------+---------------------+---------------------+------------+
| hook | next_run_gmt | next_run_relative | recurrence |
+-------------------+---------------------+---------------------+------------+
| wc_cleanup_sessions | 2025-12-27 11:05:00 | in 2 minutes | hourly |
| woocommerce_scheduled_sales | 2025-12-27 12:00:00 | in 57 minutes | hourly |
+-------------------+---------------------+---------------------+------------+
cr0x@server:~$ wp cron test
Success: WP-Cron spawning is working as expected.
What it means: WP-Cron can run. If it can’t, delayed tasks (like payment capture finalization in some setups) can lag.
Decision: If WP-Cron fails, configure a real system cron to hit wp-cron.php, and ensure outbound HTTP is allowed.
Three corporate mini-stories from the trenches
Mini-story 1: The outage caused by a wrong assumption
The company had migrated to a new load balancer. TLS terminated at the edge, origin traffic was HTTP, and everyone nodded like this was normal. The storefront looked fine. Product pages loaded. Add-to-cart worked. So the team assumed the migration was good.
Checkout, however, started failing intermittently. Not for everyone, not always. The symptom was a spinner and then a generic error. The browser console showed nothing obvious. Support tickets trickled in with screenshots that all looked the same: “Something went wrong.” Classic.
Eventually someone noticed a pattern: customers using saved payment methods failed more often. That was the clue. Saved methods leaned harder on sessions and nonces, and those were tied to cookies. Cookies, in turn, were being set inconsistently because WordPress couldn’t reliably detect HTTPS at the origin.
The wrong assumption was simple: “If the site loads on HTTPS, WordPress knows it’s HTTPS.” Behind proxies, that’s not guaranteed. The fix was equally simple: correct forwarded headers, configure the origin to trust them, and align canonical URLs. Checkout instantly stabilized, as if by magic. It wasn’t magic. It was math plus HTTP.
Mini-story 2: The optimization that backfired
A performance initiative had a noble goal: improve Core Web Vitals. The team enabled aggressive JS optimization: minify, combine, defer, delay, and “remove unused.” The homepage got faster. The graphs looked great. There were celebratory messages in the chat.
Then conversions dipped. Not cratered, just enough to trigger the “are we imagining it?” phase. Support reports mentioned payment failures, but not consistently. Some customers completed orders; others couldn’t load the card input or 3DS challenge. The logs had occasional gateway timeouts but nothing decisive.
The culprit was the optimizer delaying scripts it considered “non-critical,” including hosted payment fields. On some devices, the customer clicked Place order before the payment element initialized, so the checkout request went through without a payment token. WooCommerce did what it could: it created an order, then failed payment.
The fix wasn’t to abandon performance work. It was to stop treating checkout like a marketing page. They excluded checkout, cart, and account pages from JS combining/delaying, and added monitoring for tokenization errors. Performance remained good where it mattered, and checkout stopped quietly bleeding revenue.
Mini-story 3: The boring but correct practice that saved the day
A different team ran weekly “checkout fire drills.” Not dramatic ones. They simply ran one real transaction through each payment method in a staging environment that mirrored production configuration: CDN rules, WAF, headers, everything. They also verified webhook delivery and order status transitions.
This practice felt dull. It didn’t produce shiny dashboards. It didn’t get applause. But it did produce a muscle memory: where logs live, what “normal” looks like, and which vendor dashboards matter during an incident.
One Friday, a WAF managed rule update started blocking POSTs containing certain address patterns. The first symptom was a small rise in checkout failures. Because the team had a baseline and knew exactly where to look, they spotted 403 blocks in minutes, added a targeted exception for the checkout endpoint, and moved on with their lives.
The saving grace wasn’t heroics. It was the unglamorous habit of testing the whole payment loop, including webhooks, as part of operations—not as a once-a-year panic.
Common mistakes: symptom → root cause → fix
1) Spinner forever after “Place order”
Symptom: checkout button spins; no order created; network shows ?wc-ajax=checkout pending or failing.
Root cause: JavaScript error, blocked AJAX endpoint, or PHP-FPM queueing/timeouts.
Fix: Check DevTools console, then server access/error logs. Exclude checkout from JS optimization. Increase PHP-FPM capacity only after finding slow requests.
2) “Nonce verification failed” / session lost
Symptom: sporadic failures; often after time on page; sometimes only on mobile.
Root cause: caching a page with nonces; cookies blocked; HTTP/HTTPS mismatch; multiple domains.
Fix: disable caching on checkout/cart/account, enforce one HTTPS hostname, confirm secure cookies and consistent siteurl/home.
3) Redirect loop between HTTP and HTTPS
Symptom: browser says too many redirects; curl shows repeated 301/302.
Root cause: WordPress URL settings disagree with proxy termination; conflicting redirect rules in Nginx/Apache, WordPress, and CDN.
Fix: choose one canonical redirect layer (prefer edge or web server), set correct WordPress site URLs, pass X-Forwarded-Proto, and remove redundant redirects.
4) Orders created but stuck “pending payment”
Symptom: customer claims paid; gateway dashboard shows success; WooCommerce order remains pending.
Root cause: webhook not delivered or rejected (signature mismatch, 403 by WAF, timeout), or site can’t process async callbacks.
Fix: verify webhook endpoint reachability, allowlist gateway IP/user-agent as appropriate, confirm secrets, and ensure server isn’t timing out on webhook processing.
5) Payment method doesn’t appear on checkout
Symptom: gateway section blank; console shows blocked scripts.
Root cause: mixed content, CSP too strict, ad-blocker-like security plugin, or minify plugin breaking script order.
Fix: eliminate HTTP assets; adjust CSP to permit gateway domains; exclude gateway scripts from minify/delay; test in clean browser profile.
6) Checkout works for admins but not customers
Symptom: you can order; customers cannot.
Root cause: caching variations by cookie; role-based behavior; security rules that exempt logged-in users; guest checkout issues.
Fix: test as logged-out user; disable caching for checkout; ensure guest checkout enabled if intended; check WAF rules applied to anonymous users.
7) Random “There was a problem processing your order” under load
Symptom: intermittent failures, often during campaigns.
Root cause: PHP-FPM max children reached, DB contention, external API timeouts, or rate limiting at gateway.
Fix: observe worker saturation and slow queries; add queueing/retries where appropriate; reduce synchronous external calls during checkout.
8) Cart empties at checkout
Symptom: items disappear; “session expired.”
Root cause: cookies not persisting (SameSite/secure), caching, or domain mismatch between www/apex.
Fix: standardize hostname, ensure HTTPS everywhere, don’t cache cart/checkout, and validate cookie attributes.
Checklists / step-by-step plan
Step-by-step: stabilize checkout in the next 30–60 minutes
- Freeze changes: no plugin updates, no CDN rule edits, no theme deploys until checkout works.
- Reproduce with evidence: DevTools console + network HAR capture, exact error message, and timestamp.
- Check access/error logs around the timestamp for
?wc-ajax=checkoutand webhook endpoints. - Check WooCommerce logs for the gateway and for fatal errors.
- Disable full-page cache for checkout/cart/account at every layer: plugin cache, server cache, CDN cache.
- Temporarily disable JS optimization on checkout: combine/minify/defer/delay.
- Confirm HTTPS canonicalization: one hostname, no redirect loops, secure cookies.
- Validate gateway configuration: API keys, webhook secret, endpoint reachable, correct environment.
- Reduce blast radius: if a specific payment method is failing, temporarily disable it and route customers to a working alternative while you fix the root cause.
- Announce status: internal stakeholders need a clear “checkout impacted” signal, not rumors.
Step-by-step: find the root cause without breaking more things
- Baseline the system: CPU, memory, disk, PHP-FPM saturation, DB threads, error rates.
- Diff the last change: plugin update, theme change, PHP upgrade, WAF rule update, CDN caching policy.
- Isolate conflicts:
- Test with default theme on staging.
- Disable non-essential plugins on staging.
- Re-enable in batches until it breaks.
- Validate webhooks end-to-end: event arrives, signature validates, order status updates, email sent.
- Write a postmortem note: what failed, why it wasn’t caught, and what you’ll monitor next time.
Operational checklist: make future checkout failures less exciting
- Alert on spikes in
4xx/5xxfor?wc-ajax=checkoutand webhook endpoints. - Track payment success rate by method (Stripe, PayPal, etc.), not just total orders.
- Exclude checkout/cart/account from caching and from “auto-optimization” tooling by policy.
- Maintain a staging environment with the same CDN/WAF policies as production.
- Run a weekly synthetic transaction including webhook confirmation.
FAQ
Why does WooCommerce checkout work for me but not for customers?
Admins often bypass caching and some security rules, and you may have different cookies/sessions. Test logged-out in incognito. If only guests fail, suspect caching, cookie scope, or WAF rules targeting anonymous traffic.
What’s the fastest way to tell if it’s a plugin conflict?
Browser console error plus a sudden checkout break after an update is a strong signal. On staging, switch to a default theme and disable all non-essential plugins. If checkout returns, you have your answer.
Do I really need HTTPS everywhere or just on checkout?
Practically: everywhere. Mixed schemes cause cookie and session weirdness, and modern browsers will punish you for partial HTTPS. Also gateways expect secure origins for many flows.
Why are orders stuck on “pending payment” even though Stripe says paid?
Because “paid” in the gateway UI is not the same as “order marked paid” in WooCommerce. Webhooks reconcile final state. If webhook delivery is blocked or signature validation fails, WooCommerce won’t update the order.
Can caching plugins break checkout even if they claim they exclude it?
Yes. Exclusions can be incomplete, overridden by CDN rules, or broken by query-string normalization. Verify with headers (cache hit/miss), and ensure the CDN is not caching personalized responses.
Why does checkout fail only on mobile Safari?
Safari’s tracking prevention and cookie handling can break third-party embedded payment fields or cross-site redirects if SameSite and secure attributes aren’t correct. Also, aggressive script delaying can race payment element initialization on slower devices.
Should I increase PHP memory limit to fix checkout?
Only if you have evidence of memory exhaustion (fatal errors, OOM kills). Increasing memory can mask inefficient plugins and worsen node density. Fix the underlying slow/failing code path first.
How do I know if my WAF is blocking checkout?
Look for 403 responses on ?wc-ajax=checkout and matched rules in WAF logs or web server error logs. If you see ModSecurity/managed-rule hits on checkout POST fields, you need a targeted exception.
What’s the most common “it was fine yesterday” cause?
A change you didn’t connect to checkout: plugin auto-update, CDN/WAF managed rule update, or an “optimization” feature turned on. Checkout is sensitive; treat changes as guilty until verified.
Is it safe to disable webhooks as a workaround?
No. Webhooks are how you reliably synchronize payment state. If webhooks are failing, fix reachability/signatures/timeouts. Disabling them just moves the work to manual reconciliation, which will fail at scale.
Conclusion: what to do next (and what to stop doing)
Here’s the practical next move set:
- Get evidence: one failed checkout attempt with timestamp, DevTools network entry for
?wc-ajax=checkout, and the relevant WooCommerce/PHP logs. - Pick the lane: SSL/cookies, gateway/webhooks, plugin/theme, cache/WAF, or server limits. Don’t debug five lanes at once.
- Stabilize first: exclude checkout from caching/optimization, remove recent changes, and keep at least one payment method working if possible.
- Fix the root cause: correct HTTPS/proxy headers, repair gateway config, tame the WAF, or eliminate the conflicting plugin.
- Make it boring: add monitoring for checkout AJAX failures and webhook health, and run a weekly synthetic transaction.
Stop randomly toggling plugins like you’re trying to open a combination lock. Checkout reliability is not a superstition problem. It’s an observability problem—follow the request, read the logs, and make one change at a time.