How to Fix “No shipping options were found” in WooCommerce
The cart or checkout says no shipping options were found for an address. Turn on shipping debug mode to see which zone matched, then fix the zone order, ZIP codes, methods or carrier plugin.
Place order spins forever or shows “There was an error processing your order”. Read the checkout request in the Network tab, check the logs, then fix the gateway, cache or script.
A shopper fills in the checkout form, clicks Place order, and then one of three things happens. The button spins and never stops. A red box appears saying “There was an error processing your order. Please check for any charges in your payment method and review your order history before placing the order again.” Or a payment gateway message appears, such as an invalid API key or a card that cannot be charged. Either way, no money is taken.
The generic message is WooCommerce telling you that the browser asked the server to process the order and got back something it could not understand: an HTTP error, a page of HTML instead of JSON, or a JSON reply with no explanation. The real cause is almost always visible in one network request and one log file. This guide is for store owners and the designers or developers who look after their stores.
On the classic checkout (the [woocommerce_checkout] shortcode), WooCommerce’s checkout.js collects the form and sends it as a POST request to https://example.com/?wc-ajax=checkout. On the server, WooCommerce checks the security nonce, checks the cart is not empty, validates the fields, creates the order with the status Pending payment, and calls the payment gateway. The gateway talks to its own API (Stripe, PayPal and so on) and returns either success with a redirect URL, or failure with a message. WooCommerce sends that back as JSON, and the script either redirects the shopper to the thank you page or prints the error.
The block checkout does the same job through the Store API instead: the browser sends a POST to /wp-json/wc/store/v1/checkout with a Nonce header, and the order exists as a hidden checkout-draft until payment is attempted.
In checkout.js, the generic message appears when the request fails at the HTTP level or the JSON has no messages in it. It is a symptom, not a diagnosis.
| What the shopper sees | What it usually means |
|---|---|
| Spinner never stops | The request is still waiting (slow gateway call, PHP timeout) or a JavaScript error stopped the script before it could show a result. |
| “There was an error processing your order” | The server returned an HTTP error, HTML, a PHP warning mixed into the JSON, or an empty failure. |
| “We were unable to process your order, please try again.” | The checkout nonce failed. Usually a cached checkout page carrying someone else’s nonce. |
| “Sorry, your session has expired.” | The nonce failed and the cart is empty on the server. Session and cookie problems are covered in FG-54. |
| A gateway message (invalid key, declined, authentication) | The gateway API rejected the call. This is configuration or the card, not WooCommerce. |
cd into the site folder, the one that contains wp-config.php. WP-CLI is called wp on most hosts and wp-cli on some container platforms. Use whichever exists.curl is built into macOS, Linux and current Windows (type curl.exe in PowerShell so the alias does not interfere). WSL also works.checkout in the filter box.?wc-ajax=checkout (classic) or checkout under wc/store/v1 (block). Read the Status on the Headers tab, then open the Response tab.| Status and response | Meaning | Go to |
|---|---|---|
200, JSON with "result":"success" and a redirect, but no redirect happened |
A script on the page broke the redirect handling | Step 5 |
200, JSON with "result":"failure" and messages |
Validation or gateway refusal; the message is the answer | Step 3 |
200, but the response starts with HTML, Warning:, Notice: or Deprecated: before the { |
PHP is printing errors into the response | Step 2 |
| 500 | PHP fatal error during checkout | Step 2 |
| 403 | A firewall, ModSecurity rule or Cloudflare WAF blocked the POST | Step 4 |
| 301, 302 or 404 | The request was redirected, often by a www or https rule, or a security plugin hiding wc-ajax |
Step 4 |
| (pending) for a long time, then 502, 504 or 524 | PHP is stuck waiting, usually on the gateway API or a slow plugin hook | Step 3 and FG-13 |
| No request at all | JavaScript failed before sending | Step 5 |
Also look at the Console tab. The classic checkout script logs Unable to fix malformed JSON when it found junk around the JSON that it could not strip. That line alone proves something on the server is printing output into the response.
To check whether the wc-ajax endpoint is reachable at all, run this on your computer:
curl -s -o /dev/null -w "%{http_code}\n" -X POST "https://example.com/?wc-ajax=get_refreshed_fragments"
Placeholder: replace example.com with your store’s domain exactly as it appears in the address bar (with or without www). Flags: -s hides progress output, -o /dev/null discards the body (use NUL on Windows), -w "%{http_code}\n" prints only the status code, and -X POST sends a POST as the real checkout does. Good output: 200. Bad output: 403 means something blocks POSTs to wc-ajax; 301 means your domain or protocol does not match what you typed, which you should fix before anything else.
WooCommerce records uncaught PHP fatal errors under the source fatal-errors. Recent versions also keep a step by step trace of each place order attempt under sources named place-order-debug- followed by a short ID. Logs for orders that completed are deleted in the background, so the ones that remain are usually the attempts that failed. Gateways write their own sources too; for Stripe it is woocommerce-gateway-stripe once you check Log debug messages in its settings.
By default these logs are files in wp-content/uploads/wc-logs/. The settings screen on the same Logs page shows the storage location, the retention period in days and the level threshold. On the server you can list and read them directly:
ls -lt wp-content/uploads/wc-logs/ | head -n 15
tail -n 40 wp-content/uploads/wc-logs/fatal-errors-YYYY-MM-DD-HASH.log
Where: server SSH, in the site folder. Placeholders: YYYY-MM-DD is the date in the file name and HASH is the random string WooCommerce appends; copy the full name from the ls output. Flags: -lt lists newest first with sizes and times, head -n 15 keeps only the top 15 lines, tail -n 40 shows the last 40 lines of the file. Good output: no fatal-errors file for today. Bad output: a line such as CRITICAL Uncaught Error: Call to undefined method ... in /wp-content/plugins/PLUGIN_SLUG/.... The path tells you which plugin or theme crashed. If the folder has moved, the Logs settings page names the real location.
If the response starts with Warning: or Deprecated:, PHP is configured to display errors on screen. The checkout itself may have worked, but the browser cannot parse the reply. On a live store, errors should go to a log, not the screen. In wp-config.php (a file you edit), make sure these lines sit above the line that says to stop editing:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );
What each does: WP_DEBUG turns on error reporting, WP_DEBUG_LOG writes it to wp-content/debug.log, and WP_DEBUG_DISPLAY plus display_errors stop it being printed into pages and AJAX replies. There are no placeholders. Good result: the checkout response becomes clean JSON and the warnings appear in debug.log instead. Turn WP_DEBUG back to false when you are done. General fatal error handling and recovery mode are covered in FG-02.
Every major gateway has two separate sets of credentials. Stripe keys start with pk_test_ and sk_test_ in test mode and pk_live_ and sk_live_ in live mode. Webhook signing secrets start with whsec_ and are different for each endpoint and each mode. PayPal has sandbox and live apps with different client IDs. Mixing them produces errors such as Stripe’s Invalid API Key provided or payments that succeed in one dashboard while the store never hears about them.
To confirm: open WooCommerce > Settings > Payments, then the gateway, and note whether test mode is checked. Check the gateway dashboard is in the same mode. If the plugin offers a connect or reconnect button, prefer it to pasting keys, because it fetches a matching set.
Turn on the gateway’s debug logging, place one test order, and read its log source. The gateway dashboard also keeps a request log of API calls and responses. A 401 there means bad keys. A 402 or a decline code means the card was refused, which is the bank’s decision and not a fault in your store.
Your server must reach the gateway API over HTTPS. If the checkout hangs and the log shows cURL error 28 (timed out) or cURL error 6 (could not resolve host), the hosting server cannot reach the gateway; that is a ticket for your host. A server clock that has drifted can also break signed requests. The Stripe plugin, for example, rejects webhooks whose timestamp is more than five minutes away from the server’s time. Check with date -u over SSH and compare it with the real UTC time.
Webhooks, which tell your store a payment completed after the shopper has left, are the subject of FG-53. If orders are created but stay in Pending payment, go there.
WooCommerce sends no-cache headers on the cart, checkout and my-account pages and defines DONOTCACHEPAGE so well-behaved cache plugins skip them. A server cache or CDN rule that ignores those signals will serve one shopper’s checkout HTML to the next, complete with a nonce that belongs to a different session. The result is “We were unable to process your order, please try again.” on the first click. Check the headers from your computer:
curl -sI "https://example.com/checkout/" | grep -iE "cache-control|x-litespeed-cache|cf-cache-status|x-cache|age"
Placeholder: use the real address of your checkout page; WooCommerce > Settings > Advanced shows which page is assigned. Flags: -s silences progress, -I requests headers only, and grep -iE filters case-insensitively for the cache headers. Good output: a cache-control line containing no-cache and max-age=0, and any cache status showing MISS, BYPASS or DYNAMIC. Bad output: HIT, or an age header with a number above zero. Run it twice, since the first request can be a miss that primes the cache. The exact cache exclusions for LiteSpeed, Nginx and Cloudflare are in FG-54.
A 403 on ?wc-ajax=checkout nearly always comes from a web application firewall. Apostrophes in names or text in order notes can trip a ModSecurity rule. Check the security plugin’s block log and Cloudflare Security Events for the test time, and allow the specific rule rather than disabling the firewall. FG-49 covers 403 diagnosis in depth.
A 301 or 302 means the POST was redirected to another host and its body was lost. Make sure WordPress Address and Site Address in Settings > General match the canonical domain; FG-39 covers that setup.
If no checkout request is sent, or the gateway’s card field never appears, look at the Console tab. Two families of error matter.
Script errors from optimization. Delay JavaScript, combine, defer and minify features can load jquery, wc-checkout or the gateway’s scripts in the wrong order or only after the first scroll or click. Errors such as jQuery is not defined or wc_checkout_params is not defined are the giveaway. Exclude the checkout and cart pages from all JavaScript optimization, or exclude the gateway domains and WooCommerce’s checkout scripts. Cloudflare Rocket Loader causes the same pattern and should be off for checkout. The general method for finding which optimization broke a script is in FG-47.
Content Security Policy blocks. A message such as Refused to load the script 'https://js.stripe.com/v3/' because it violates the following Content Security Policy directive means a security header plugin or server rule does not allow the gateway. Stripe needs at least https://js.stripe.com in script-src and frame-src, https://hooks.stripe.com in frame-src for 3D Secure, and https://api.stripe.com in connect-src. Copy each gateway’s current list from its own documentation, because the lists change.
To confirm, switch the optimization feature off on staging (or for the checkout page only) and test again.
Edit the page assigned as Checkout. If it contains the Checkout block, you run the block checkout. If it contains the shortcode [woocommerce_checkout] (or a Classic Checkout block), you run the classic one. New stores get the block checkout by default.
| Feature | Classic (shortcode) | Block |
|---|---|---|
| Request on Place order | POST /?wc-ajax=checkout |
POST /wp-json/wc/store/v1/checkout |
| Security token | woocommerce-process-checkout-nonce form field |
Nonce request header (error woocommerce_rest_invalid_nonce) |
| Field customization | PHP hooks such as woocommerce_checkout_fields and template overrides |
Those hooks do not change the block form; it uses its own extensibility API |
| Gateway support | Any gateway | The gateway must register block support; in the editor an unsupported one is marked “Incompatible with block-based checkout” |
| Blocked by | Firewalls filtering wc-ajax |
Security plugins that disable or restrict the REST API |
Two traps follow. A field editor written for the classic checkout does nothing on the block checkout. A security plugin that blocks /wp-json/ for logged-out visitors breaks the block checkout while the classic one keeps working. If a gateway or plugin you depend on is not block compatible, switching the page back to the classic checkout is a legitimate fix. The Checkout block offers a switch to the classic checkout in current versions; if your version does not, replace the block with a Shortcode block containing [woocommerce_checkout].
If the logs show nothing clear, isolate the conflict. On staging, switch to a default theme such as Storefront or Twenty Twenty-Five and deactivate everything except WooCommerce and the gateway. On a live store, the Health Check & Troubleshooting plugin’s troubleshooting mode does the same thing only for your own logged-in session, so customers are not affected.
wp plugin list --status=active --fields=name,version,update_version
Where: server SSH, site folder. Flags: --status=active limits the list to active plugins, and --fields picks the columns. No placeholders. Good output: a table to save as a record before you change anything. Plugins that touch checkout (field editors, fraud screening, address autocomplete, currency switchers) are the first suspects. Re-enable them one at a time with a test order after each.
Sometimes the checkout works but a payment option simply is not there: the PayPal buttons do not appear, the Stripe card field is missing, or Apple Pay and Google Pay never show. Gateways decide for themselves whether to offer a method on each page load, and they hide it quietly when a condition fails. Work through these checks in order.
Both PayPal and Stripe plugins connect your store to an account through an onboarding flow. If that flow was not finished, or was finished in the wrong mode, the buttons do not load.
A very common case: the store was built on a staging domain with sandbox or test credentials, then moved to the live domain. The gateway is still connected in sandbox mode, connected to the staging site’s account, or has keys from one mode and a setting from the other. Switch the gateway to live mode and reconnect through its connect button rather than pasting keys by hand, then place a low-value real order and refund it. Test mode versus live mode is covered in Step 3.
PayPal’s buttons load from www.paypal.com/sdk/js and Stripe’s fields from js.stripe.com. If those scripts are delayed, deferred, combined, blocked by a Content Security Policy, held back by a cookie consent banner, or blocked by the shopper’s ad blocker, the method appears empty or not at all. Open the Console tab on the checkout and look for Refused to load the script or errors that mention paypal or Stripe. Step 5 above covers optimization and Content Security Policy fixes, and the cookie consent guide covers consent tools that block payment scripts. Ask the customer to try with browser extensions turned off if only some people report the problem.
If none of these explain it, test with a default theme and only WooCommerce and the gateway active (Step 7), then send the gateway’s support team your System Status report and the Console errors.
When a problem is intermittent, collect four views of the same attempt and line them up by time.
place-order-debug- log. Each line is one step the order passed through, with context such as the order ID and, where PHP reported one, the last error. The last step written is where the attempt died. If an order ID was assigned, the order stores the log source in its meta so you can match them.grep "wc-ajax=checkout" /PATH/TO/access.log | tail -n 20
Where: server SSH. Placeholder: /PATH/TO/access.log is your site’s access log; ask your host if you cannot find it. For the block checkout, search for wc/store/v1/checkout instead. Good output: lines with status 200. Bad output: 403, 500 or 504 at the times shoppers complained. Some log formats include request time at the end of the line; a value close to your PHP max_execution_time or the proxy timeout means the request was killed for running too long.
If the trace stops at the step where the gateway is called and the gateway dashboard shows no request, the call never left your server (outbound firewall, DNS, or PHP timeout). If the dashboard shows a successful charge but the trace stops afterward, the payment worked and something after it crashed: look for a fatal error from a plugin hooked to order status changes, such as an invoicing, CRM or stock sync plugin.
Open a ticket when the evidence points below WordPress: 403s you cannot trace to a plugin, 502 or 504 errors on checkout, outbound connection failures to the gateway, a wrong server clock, or a server cache you cannot configure. Include:
/?wc-ajax=checkout or /wp-json/wc/store/v1/checkout), the status code and the response headers.cf-ray header.fatal-errors, place-order-debug- or the gateway log, with customer details removed.The diagnostic tools page helps you collect headers first. If sales are stopped right now, start at the Emergency Room.
Did this fix it?
Still stuck? Start again from what you can see in the Emergency Room.
Still stuck?
Print a free one-page checklist to keep, or open the Emergency Room if the symptom does not match.
Get the free checklistsKeep reading
Search the field manual
Or jump straight to a symptom
Press / to search from any page, Esc to close