Why a Slow admin-ajax.php Looks Like an Attack
admin-ajax.php is the single AJAX endpoint for all of WordPress. Heartbeat, dashboard widgets, page builders, cart fragments, live search - every interactive feature posts to that one file instead of loading a page.
The expensive part is the boot. Each call runs a full WordPress load - plugins_loaded, init, admin_init - inside wp-admin context, then fires the action hook. A plugin that burns 400 ms on admin_init pays that cost on every call that arrives, whichever action triggered it.
CPU climbs and the access log fills with POST /wp-admin/admin-ajax.php, each request answering in seconds. The frontend can still feel fine because the page cache covers it, while every open editor tab adds another stream of calls to the same endpoint.
So two different complaints land on the same file: each call is slow, and the calls never stop. Both start with the same question - what is actually running inside those requests?
Who Keeps Calling admin-ajax.php
Before blaming a botnet, count the callers. The repeat offenders are boring:
| Caller | What it does |
|---|---|
| Heartbeat | The post editor posts to admin-ajax.php about every 15 seconds while it is open. Three tabs on the same post means three streams of calls. |
| Polling plugins | Backup dashboards, security scanners, chat widgets and analytics panels hit the endpoint on a timer, whether anyone is watching or not. |
| Frontend ajax | Cart fragments, filters and form handlers with a nopriv hook run for logged-out visitors too, so real traffic adds its own load. |
| Your own handler | Every plugin or theme that registers a wp_ajax_ action shares the same endpoint and the same boot cost. |
The Network tab in your browser shows the action parameter on the slow calls, and the access log shows the rate. That tells you which stream it is. It does not tell you why the calls cost seconds.
Profile a Request, See the Callback
Knowing the action does not name the cause. Around it sits the whole plugin stack - every callback hooked to plugins_loaded, init, admin_init and the action itself runs on that call.
Find Slow Callbacks (Pro) profiles one URL on your site. Paste admin-ajax.php itself - or the admin page that fires the slow calls - and it fires a hidden request carrying your admin cookies, times every callback over the threshold, and ranks them by milliseconds with the hook and the source file attached.
Because the whole plugin stack boots on every call, the result table answers two questions at once: which callback makes every ajax call slow, and which one only ran inside your action. A plugin burning 400 ms on admin_init sits on top - it slows every call the same way it slows every admin page.
Sessions are saved. Profile, remove or fix the offender, profile again - the before and after sit in the same history table.
Any same-site URL can be profiled, not just the ajax endpoint - the same request answers the slow wp-admin complaint when whole admin screens crawl.
When the Slow Part Is One Query
Sometimes the callback is a thin shell and the seconds live inside a single query - an unindexed meta lookup, a table scan on every poll, a search running a LIKE over a big table.
Slow Query Analyzer logs every query slower than its threshold - 0.05 s by default - with the caller, then runs EXPLAIN on each capture and suggests a ready-to-run index. admin-ajax.php counts as an admin request, so the default admin-only mode catches ajax calls with no setting to change.
The two read the same request from different ends. The profiler says "this callback took 4 s"; the analyzer says "and 3.8 s of it was this SELECT on wp_postmeta". Together they point at one file and one line of SQL.
Know the callback and the query? That is most of the diagnosis - the fix is a patch, a replacement, or a bug report with the numbers attached.
Pro starts at $79/year →What the Modules Will Not Do - Worth Knowing First
- The profiler sends a GET. It cannot replay the exact POST your browser sent, so a handler that only reads $_POST fields sees an empty request and exits early. For those, profile the admin page that fires the call - the boot callbacks every ajax call shares still get timed.
- A nonce-guarded handler dies first. Its own work stays unmeasured on the profiled request; everything around it - plugins_loaded, init, admin_init - still gets timed.
- Loopback HTTP is required. The module profiles by requesting your own site, so a firewall or host that blocks loopback requests blocks the profiler.
- Find Slow Callbacks ships off by default. It deploys an MU-plugin profiler, so you switch it on when you need it - nothing runs until then.
- Nothing here throttles Heartbeat or rate-limits ajax. The modules name the callback and the query; removing, replacing or reporting the offender stays your call.
admin-ajax.php FAQ
Why is admin-ajax.php so slow in WordPress?
Each call is a full WordPress bootstrap plus the handler. A plugin doing heavy work on init or admin_init slows every call, and one slow query inside a handler does the same for that action. High call volume from Heartbeat and polling plugins multiplies whatever each call costs.
Is the admin-ajax.php traffic an attack?
Usually your own admin. The post editor's Heartbeat posts about every 15 seconds per open tab, and dashboard widgets poll on timers - in the access log it reads exactly like hammering. Check the action parameter on the calls; real floods exist, but friendly fire is the common case.
Can I just disable admin-ajax.php?
No. wp-admin depends on it - autosaves, updates, most interactive screens - and plenty of frontends do too. Blocking the endpoint trades a slow site for a broken one. The slow callback or the chatty caller is what needs fixing.
Does the Heartbeat API slow down WordPress?
Heartbeat is the top repeat caller inside wp-admin, and every beat is a full WordPress load, so on a heavy site each beat pays the full boot cost. Nothing in WP Multitool throttles it - the profiler shows you what each beat actually costs instead.
How do I find which plugin makes admin-ajax.php slow?
Two angles. Find Slow Callbacks times every callback in a profiled request and ranks them with hook and source file, so it names the plugin slowing every call. Slow Query Analyzer logs the queries a call runs and flags the ones over threshold with EXPLAIN suggestions.
Does the profiler replay the ajax call my browser made?
No - it fires a GET to the URL you give it, carrying your admin cookies. A handler that needs POST fields or a valid nonce exits early, so its own work stays unmeasured while the boot around it still gets timed. For those, profile the admin page that fires the call.
Will a caching plugin fix a slow admin-ajax.php?
No. Ajax responses are dynamic and deliberately uncached, so a page cache never touches them. Every call pays full PHP plus the handler, which is why making the call itself cheaper is the only real lever.
Name the Callback Behind Every AJAX Call
Find Slow Callbacks and Slow Query Analyzer are Pro modules - $79/year for one site, and every paid plan includes all 19 modules. Switch the profiler on when the access log starts filling up.
Get Pro - from $79/year Slow Admin Guide All plans on the pricing page →