admin-ajax.php is WordPress’s front door for asynchronous requests: cart fragments, form submissions, autosaves, builder saving, filter widgets. When it fails, the symptom is indirect — a spinner that never resolves, a cart that will not update, “Saving…” forever. The diagnosis is easier than it looks because every AJAX request identifies its action in plain sight.
Trace the failing request in 60 seconds
- Open the browser network tab (F12 → Network), trigger the broken behavior.
- Find the admin-ajax.php request — filter by “ajax”. Click it: the Payload tab shows
action=the_action_name; the Response tab shows what came back: a 500 error body, a 0, a JSON error, or an HTML login page. - Status codes mean different things:
400— the action handler rejects the data (plugin bug or missing nonce);500— fatal error server-side (checkdebug.log, the line names the file);403— something blocked it: a security plugin’s firewall or mod_security; response is an HTML login page — session/nonce expired.
The common culprits, by symptom
- Spinner forever on a form: the action’s handler fatals — the 500’s debug.log line names the plugin. One deactivation confirms.
- Cart fragment not updating: cache plugin excluding or caching the AJAX call wrong — the cache-layer analysis is in the cache comparison.
- Admin saves failing intermittently: Heartbeat throttling gone too aggressive (see the Heartbeat guide) or nonces expiring on long-lived tabs.
- Everything AJAX fails after a migration: stale site URL in the database — AJAX posts to the old domain and gets CORS-blocked. The migration checklist covers the URL fix.
- Timeouts on heavy actions:
curl error 28-style timeouts inside plugins calling slow external APIs — the cURL timeout guide covers that pattern.
Deeper: Query Monitor
When the network tab is ambiguous, Query Monitor logs every AJAX request server-side with its timing and the hook that handled it — including which plugin registered the failing action. That last detail shortens the path from “something broke” to “this plugin’s handler” considerably.
The action handler rejected the request — missing/invalid nonce, malformed payload, or a plugin bug in the handler. The response body usually contains the specific error message. Something blocked it before WordPress: a security plugin's firewall rule, mod_security, or rate limiting. Whitelist the endpoint in the blocking layer, or the AJAX action in the plugin. The network tab shows action=the_name in the payload; Query Monitor maps the action to the plugin that registered its handler. Then test with that plugin deactivated. Stale URLs: AJAX posts to the old domain and browsers block the cross-origin response. Search-replace the old URL in the database (wp search-replace) and clear caches.Why does admin-ajax.php return 400?
Why is admin-ajax.php returning 403?
How do I find which plugin handles an AJAX action?
Why did AJAX break after moving my site?