Skip to content
  • FG-33
  • 13 min read
  • Last checked November 7, 2026

How to Fix a Slow WordPress Admin (wp-admin and Dashboard Taking Forever)

The front end is fine but wp-admin crawls. Find the real cause: Heartbeat and admin-ajax, update checks, WP-Cron, Action Scheduler backlogs or heavy list screens.

Run the health check

Your public site loads quickly, but the moment you log in everything drags. The Dashboard takes ten seconds to appear, the Posts list spins, saving a page hangs, and the Plugins screen feels like it is downloading the internet. Sometimes the browser tab shows a request to admin-ajax.php that never seems to finish.

This is a very common pattern, and the reason is simple. Visitors get pages from a full page cache, so they never touch PHP. wp-admin is never cached: every screen is built from scratch by PHP for a logged-in user, and on top of that the admin runs background jobs, remote update checks and repeated AJAX calls. Anything slow in your stack shows up in the admin first. This guide is for site owners, editors and developers who live in the dashboard.

What is happening

Each admin page load runs WordPress core, every plugin and the theme’s admin code, then a set of admin-only tasks. Several of those tasks talk to other servers or kick off more PHP requests in the background.

Source of load What it does Typical symptom
Heartbeat API JavaScript in the admin sends a POST to admin-ajax.php every 60 seconds by default (slower when the tab is hidden), for post locking, autosave and login expiry. High CPU when several editors leave tabs open; slow admin under load.
Other admin-ajax calls Plugins use the same endpoint for notices, stats widgets and live counters. A screen loads, then keeps spinning while AJAX finishes.
Update and license checks Core, plugins and themes call remote servers to look for updates. Premium plugins often call their own servers. Plugins and Updates screens take many seconds; sometimes every admin page does.
WP-Cron Scheduled tasks run when someone loads a page, triggered by a background request to wp-cron.php. Random slowdowns, heavy spikes when overdue tasks pile up.
Action Scheduler A job queue used by WooCommerce and many other plugins, stored in its own database tables. Huge tables, slow queries, constant background work.
List screens Posts, Orders and Media lists run count, search and meta queries. One specific screen is slow; others are fine.

Before you start

  • Access: an administrator account, and SSH for the WP-CLI steps. The code snippets go in a small file you create in wp-content/mu-plugins/ (create the folder if it does not exist). Files there load automatically and survive theme changes.
  • Backup: take a full backup before editing wp-config.php, deleting Action Scheduler data or changing cron.
  • Running commands: connect over SSH and cd into the folder that contains wp-config.php. Use wp, or wp-cli if that is what your host provides. Browser checks use devtools (F12) in Chrome, Edge or Firefox.
  • Front end also slow? If public pages are slow for logged-out visitors too, start with the server response guide (FG-32), then come back here.

Step 1: Name the slow component with Query Monitor

Install Query Monitor and open the slowest admin screen. In the admin bar you will see the page generation time, memory and query count. Click it.

  • Overview: if total time is, say, 6 s and database time is 0.3 s, the database is not the problem. Look at HTTP calls and PHP work.
  • HTTP API Calls: lists every outbound request with its URL, status and duration. A row taking 5 s or ending in a timeout error is a strong lead. The URL usually names the plugin vendor.
  • Queries by Component: shows which plugin runs the most and the slowest queries on this screen.
  • Admin Screen: confirms the screen ID, which you need for targeted fixes.

Query Monitor also catches AJAX requests: when an admin-ajax.php call runs, its data is added to the response headers, and Query Monitor shows warnings for AJAX errors in the browser console.

Step 2: Check Heartbeat and admin-ajax.php load

Confirm it in the browser

Open devtools, go to the Network tab, type admin-ajax in the filter box and leave the screen open for two minutes. Click each request and open the Payload tab. action: heartbeat is Heartbeat. Any other action name belongs to a plugin. Note the Time column for each.

Good: one Heartbeat request roughly every minute, each finishing in well under a second. Bad: requests every few seconds, a plugin action firing on every screen, or individual calls taking several seconds.

Confirm it in the server logs

grep "admin-ajax.php" ACCESS_LOG | awk '{print substr($4,2,17)}' | uniq -c | tail -20

Where: server SSH. Placeholder: ACCESS_LOG is your site’s access log path; common locations are /var/log/nginx/access.log, /var/log/apache2/access.log, or a logs folder in your hosting account. What it does: counts admin-ajax requests per minute (the substr trims the timestamp to the minute). Good: a handful per minute per active editor. Bad: dozens or hundreds per minute, which means many open tabs or a plugin polling aggressively.

Fix: slow Heartbeat down, keep it where it matters

Heartbeat matters most on the post editor, where it handles post locking (warning two editors off the same post) and session expiry. Elsewhere you can slow it down. Create wp-content/mu-plugins/heartbeat-tune.php:

<?php
add_filter( 'heartbeat_settings', function ( $settings ) {
    $settings['interval'] = 120;
    return $settings;
} );

This sets the tick to 120 seconds. The value is in seconds. You can see the effect in the Network tab: Heartbeat requests should now be about two minutes apart. If you prefer a settings screen, the Heartbeat Control plugin does the same per area (dashboard, front end, editor). Avoid disabling Heartbeat entirely on the editor; you lose post locking and autosave warnings.

For plugin actions that poll too often, look in that plugin’s settings for live stats or real-time features and turn them off, or remove the plugin’s dashboard widget (Step 3).

Step 3: Slow dashboard widgets

If only the main Dashboard screen is slow, widgets are the usual cause. Plugins add stats panels that query analytics services, calculate reports from your database, or fetch news feeds. Core’s “WordPress Events and News” widget also fetches remote feeds, though it loads them in the background and caches them.

Hiding a widget through Screen Options only hides it visually; the server still builds it. To stop the work, remove it. Find each widget’s ID by right-clicking it, choosing Inspect, and reading the id attribute on its outer box. Then add to a mu-plugin file:

<?php
add_action( 'wp_dashboard_setup', function () {
    remove_meta_box( 'dashboard_primary', 'dashboard', 'side' );
    remove_meta_box( 'WIDGET_ID', 'dashboard', 'normal' );
}, 999 );

Placeholders: WIDGET_ID is the ID you read in Inspect. The third argument is the column, normal or side; if removal does not work, try the other one. dashboard_primary is the Events and News widget. The priority 999 makes the code run after plugins have added their widgets. Confirm: reload the Dashboard and compare the Query Monitor time before and after.

Step 4: Plugin update checks and other external HTTP requests

WordPress checks for core, plugin and theme updates on a schedule, and runs extra checks when you open the Updates, Plugins or Themes screens. Premium plugins and themes usually add their own update and license checks against the vendor’s server. If that server is slow or unreachable, the admin waits for it, often for the full timeout.

Confirm: Query Monitor’s HTTP API Calls panel on the Plugins screen. Look for slow rows to api.wordpress.org (rare, but your server’s outbound network or DNS may be at fault) and for vendor domains.

Prove it: on a staging copy, add to wp-config.php above the “stop editing” line:

define( 'WP_HTTP_BLOCK_EXTERNAL', true );
define( 'WP_ACCESSIBLE_HOSTS', 'api.wordpress.org,downloads.wordpress.org' );

This blocks every outbound request made through the WordPress HTTP API except to the hosts you list (comma separated, wildcards like *.example.com allowed). If wp-admin becomes fast, an external call was the cause. Do not leave this on in production: it also blocks payment gateways, license activations, SMTP APIs and anything else that needs the internet.

Fix: update the plugin (vendors often fix chatty checks), report the slow endpoint to the vendor, or replace the plugin. If calls to api.wordpress.org are slow, test from the server with curl -sI https://api.wordpress.org/; slow results there are a hosting network or DNS issue to raise with your host.

Step 5: WP-Cron on page load versus a real cron

WP-Cron is not a real scheduler. On each page load, WordPress checks whether any scheduled events are due and, if so, fires a background request to wp-cron.php. On a low-traffic site, events run late. On a busy site, cron fires constantly. If a job is heavy (backups, imports, email sends, sitemap builds), it competes for the same PHP workers your admin needs.

Check the queue

wp cron event list --fields=hook,next_run_relative,recurrence
wp cron test

Where: server SSH, site folder. --fields limits the columns to the hook name, when it next runs and how often it repeats. Good: next runs in the future, a modest list. Bad: many rows saying now or showing past times, which means events are overdue and piling up, or hundreds of entries from the same plugin. wp cron test checks that WordPress can reach wp-cron.php; it should report success.

Switch to a system cron

First add this line to wp-config.php above the “stop editing” line, so page loads stop triggering cron:

define( 'DISABLE_WP_CRON', true );

Then add a real cron job. In your hosting panel’s cron section, or with crontab -e as the site’s user on the server:

*/5 * * * * cd SITE_ROOT && WP_PATH cron event run --due-now --quiet

Placeholders: SITE_ROOT is the full path to the folder containing wp-config.php (run pwd there to get it). WP_PATH is the full path to WP-CLI (run which wp or which wp-cli); cron has a minimal environment, so a bare wp may not be found. */5 runs it every five minutes. --due-now runs only events that are due. --quiet suppresses normal output so cron does not email you every five minutes.

If you cannot use WP-CLI in cron, request the cron URL instead: */5 * * * * curl -s https://example.com/wp-cron.php?doing_wp_cron > /dev/null. Confirm it works: wait ten minutes and run wp cron event list again. Nothing should be overdue.

Step 6: Action Scheduler backlogs

Action Scheduler is a job queue bundled with WooCommerce and many plugins (email marketing, subscriptions, SEO, backups). It stores jobs in the wp_actionscheduler_actions and wp_actionscheduler_logs tables. If jobs fail repeatedly or pile up faster than they run, these tables grow to millions of rows and every query against them slows down. You can see the queue in the admin at Tools, Scheduled Actions (or WooCommerce, Status, Scheduled Actions).

wp action-scheduler status

Where: server SSH. The command exists when Action Scheduler is active. Output: the data store, the runner, the version, and a table with each status (pending, complete, failed, canceled, in-progress), its count, and the oldest and newest dates. Good: a small pending count with recent dates. Bad: pending actions dating back weeks (the queue is not running), thousands of failed actions (a plugin keeps erroring), or millions of completed rows.

Find what is failing

wp action-scheduler action list --status=failed --per_page=20 --fields=id,hook,group,scheduled_date

--status filters by state, --per_page limits the rows (the default is small, so set it), and --fields chooses columns. The hook column names the job, which usually names the plugin. Open one in Tools, Scheduled Actions to read its log message, then fix or remove the plugin responsible.

Run and clean the queue

wp action-scheduler run --batch-size=50 --batches=10
wp action-scheduler clean --status=complete,canceled --before='14 days ago' --batch-size=500

run processes due pending actions now; --batch-size is actions per batch and --batches the number of batches (0 means keep going until done). clean deletes old actions: --status takes a comma separated list with no spaces, --before takes a date string (default 31 days), --batch-size defaults to 20. Warning: clean permanently deletes rows. Back up the database first, and do not clean pending actions unless you know the plugin can recreate them. Large deletes are covered in more depth in the database bloat guide (FG-34).

Step 7: Large post lists and meta queries

If one list screen is slow and others are fine, the screen’s queries are the issue. Query Monitor’s Slow Queries panel will show them.

  • Items per page: open Screen Options and lower “Number of items per page”. 200 rows with many columns is expensive.
  • Extra columns: SEO, custom field and page builder plugins add columns that each run a query per row. Turn them off in Screen Options or the plugin settings.
  • The months dropdown: on sites with very large post tables, the date filter query can be slow. It can be disabled with the disable_months_dropdown filter: add_filter( 'disable_months_dropdown', '__return_true' );
  • Searching: admin search scans titles and content with LIKE; plugins that extend it to search meta values make it far slower on big tables.
  • WooCommerce orders: stores on the legacy posts table can switch to High-Performance Order Storage at WooCommerce, Settings, Advanced, Features. It moves orders into dedicated, indexed tables. Follow WooCommerce’s sync and compatibility steps and test on staging first.

Step 8: The editor loads slowly

The block editor is a JavaScript application that loads data through the REST API. Open devtools, Network, filter by wp-json, and reload the editor. Sort by the Time column.

  • One REST call takes seconds: click it and read the URL. A slow /wp/v2/users call on sites with thousands of users, or a plugin’s own endpoint, points to the cause. Test that plugin on staging.
  • Remote patterns: the editor can load patterns from the WordPress.org pattern directory. If your server’s outbound connection is slow, disable it with add_filter( 'should_load_remote_block_patterns', '__return_false' );
  • Custom fields panel: enabling it (Options menu, Preferences, Panels) runs a query over all meta keys that is slow on large sites. Turn it off if you do not need it.
  • Browser side: if Network requests are fast but the editor still lags while typing, the cost is in the browser: very long posts with hundreds of blocks, or heavy editor plugins. Test in a clean browser profile with extensions off.

If your page builder’s editor is the slow part rather than the block editor, check the builder’s own system status screen and its PHP memory requirement.

Advanced: Profiling a single admin request from the command line

The wp profile package can profile admin requests as well as front-end pages, which helps when a screen is slow only for logged-in users. Install it and run it as a specific user:

wp package install wp-cli/profile-command:@stable
wp profile stage --url=https://example.com/wp-admin/ --user=ADMIN_USERNAME --spotlight
wp profile hook admin_init --url=https://example.com/wp-admin/ --user=ADMIN_USERNAME --spotlight --orderby=time --order=DESC

Placeholders: ADMIN_USERNAME is an administrator’s login name. --user is a global WP-CLI flag that runs the command as that user, so capability checks pass. --spotlight hides near-zero rows. admin_init is the hook most admin-only plugin code attaches to; also try admin_menu, current_screen and admin_notices. Good: no callback above a few tens of milliseconds. Bad: a single callback taking hundreds of milliseconds, which names the function and therefore the plugin. Note that WP-CLI simulates the admin context and does not fire every hook a browser request would, so treat results as a strong hint and confirm with Query Monitor.

A persistent object cache (Redis or Memcached) helps the admin more than the front end, because admin requests are never page-cached and repeat many of the same option and meta lookups. If the admin is sluggish across every screen and profiling shows many small queries rather than one big one, ask your host about enabling one.

When to contact your host

Open a ticket when outbound requests to api.wordpress.org are slow from the server, when you cannot set a system cron, or when the admin is slow even with all plugins deactivated on staging. Include:

  • The slow admin URLs and the times you tested, in UTC.
  • Query Monitor totals and any slow HTTP calls with their URLs and durations.
  • The output of wp cron event list and wp action-scheduler status.
  • admin-ajax request counts per minute from the access log.
  • Recent changes: new plugins, a WooCommerce update, a PHP version change, a migration.

For quick outside checks, see our diagnostic tools. If wp-admin is not just slow but returning errors or timing out, use 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 checklists

Keep reading

All field guides

Search the field manual

What is going wrong?

Press / to search from any page, Esc to close