WordPress Heartbeat API: what it is and how to control it

The Heartbeat API is the built-in WordPress polling system that powers autosave, post locking, and real-time admin notifications. It also shows up as one of the most common suspects when a hosting provider reports high admin-ajax.php traffic. This article explains what Heartbeat actually does, how to tell it apart from WooCommerce cart fragments, and how to throttle it safely without breaking the editor.

A hosting provider email that says "your site is spiking CPU through admin-ajax.php" usually names the Heartbeat API as the culprit. Sometimes that is right. Often it is not. The Heartbeat API is a real mechanism that does generate load, but the most expensive admin-ajax.php traffic on a typical WooCommerce site is not Heartbeat at all. This article covers both sides: what Heartbeat actually does, when it causes problems, how to tell it apart from the things people confuse it with, and how to throttle it without breaking the editor.

What the Heartbeat API does

The Heartbeat API is a browser-to-server polling system that WordPress opens from any logged-in admin tab. It was introduced in WordPress 3.6 to support four specific features:

  • Autosave. The block editor posts the current draft back to the server on a regular interval so that a browser crash or a network drop does not lose unsaved work.
  • Post locking. When you open a post for editing, Heartbeat keeps reminding the server that you still have the lock. If a second editor opens the same post, they see a clear "user X is currently editing this post" warning instead of silently overwriting the first editor's changes.
  • Dashboard sync. Screens like the activity widget, the plugin update counter in the toolbar, and the "at a glance" panel refresh their numbers through Heartbeat ticks rather than requiring a full page reload.
  • Plugin notifications. Plugins can hook into the heartbeat_received PHP filter to push notifications from the server back to the admin (e.g. "a new order just came in", "your security scan finished").

Each tick is a real POST /wp-admin/admin-ajax.php request with an action=heartbeat body. PHP-FPM boots WordPress, the active plugins load, any heartbeat_received handlers run, and the response comes back as JSON. It is the same request shape as every other admin-ajax.php action, which is precisely why it shows up in hosting dashboards grouped with the rest of admin-ajax.php traffic.

When and where Heartbeat runs

Where Heartbeat is loaded matters more than most people realize, because it directly affects whether throttling is safe or pointless.

In the post editor. Default interval: 15 seconds. This is the fastest tick because autosave and post locking need to feel responsive. The Heartbeat API plugin handbook documents the 15-120 second allowed range, and 15 seconds is the editor default.

On other wp-admin screens. Default interval: 60 seconds. The dashboard, the plugins screen, the users screen, the WooCommerce orders screen: all of them tick once a minute as long as the tab is open and focused.

On the public front end. Not loaded at all by default. WordPress core does not enqueue the heartbeat script for logged-out visitors. For logged-in users on the front end (e.g. someone browsing the site while also being logged into the admin in another tab), plugins and themes can choose to enqueue it, but nothing in core forces this. The common claim that "Heartbeat runs every 120 seconds on the front end" is a misreading: 120 seconds is the upper end of the allowed interval range, not a front-end default.

When the browser tab loses focus. Heartbeat slows itself automatically. The wp.heartbeat.interval() JavaScript method exposes a "slow" mode that extends the polling gap to roughly two minutes when the tab is minimized or unfocused. That means an editor tab left open in a background window generates roughly half the traffic of the same tab in focus.

How Heartbeat surfaces as admin-ajax.php load

The reason Heartbeat shows up in hosting dashboards is mechanical, not mysterious. Every tick is an uncacheable POST request to an admin endpoint that runs WordPress end to end:

  1. The browser fires POST /wp-admin/admin-ajax.php with action=heartbeat in the body.
  2. nginx or Apache hands the request to PHP-FPM.
  3. A PHP worker boots WordPress: autoload options, active plugins, wp-includes.
  4. WordPress validates the nonce, dispatches the heartbeat_received filter, and collects any data plugins want to push back.
  5. The response is returned as JSON. The worker is now free.
  6. The browser schedules the next tick.

That is one full WordPress request per tick, per open admin tab, per logged-in user. On a one-person site with one editor tab open, this is roughly 4 requests per minute in the editor or 1 request per minute on the dashboard, and the CPU cost is negligible. On a site with five simultaneous editors each with a tab open, it climbs to roughly 1,200 editor ticks per hour if everyone is writing, plus a background stream of dashboard ticks. As the high CPU usage article explains, the CPU pressure on a WordPress server comes from uncached PHP requests, and every Heartbeat tick is one of those.

Caching does not help. Heartbeat requests are POST requests to admin-ajax.php, which every major page cache (WP Rocket, LiteSpeed Cache, W3 Total Cache, Varnish, nginx fastcgi_cache) excludes by default. There is no cacheable response because each tick returns user-specific JSON. The correct remediation is always to tick less often, not to cache more aggressively.

Heartbeat vs. WooCommerce cart fragments: telling them apart

This is the single most important diagnosis you can make, because it determines whether throttling Heartbeat will change anything at all. On a typical WooCommerce store with admin-ajax.php spikes, the traffic is almost never Heartbeat. It is the WooCommerce cart fragments feature, and the two mechanisms are completely separate.

Heartbeat. Fires only from logged-in admin tabs (post editor, dashboard, plugin screens). The request body contains action=heartbeat. Each tick touches WordPress and returns admin-scoped JSON.

WooCommerce cart fragments. Fires from the front end on every page load for any visitor with a session cookie, including logged-out shoppers with nothing in the cart. The request URL contains wc-ajax=get_refreshed_fragments. It exists to refresh the mini-cart widget in the header after a cache hit, because the cached HTML does not know the visitor's cart state. On a busy store with front-page caching, this can easily generate 20,000+ requests per hour, none of it Heartbeat.

How to tell which one is hitting you. Open the server access log (or the hosting panel's log view) and look at the admin-ajax.php request lines. The distinguishing signal is in the URL query string or the POST body:

  • POST /wp-admin/admin-ajax.php with body action=heartbeat means Heartbeat. Throttling heartbeat_settings will reduce this.
  • GET /?wc-ajax=get_refreshed_fragments or POST /?wc-ajax=get_refreshed_fragments means WooCommerce cart fragments. Throttling Heartbeat changes nothing here; the fix is to disable cart fragments via the WooCommerce filter or a cache bypass rule.
  • POST /wp-admin/admin-ajax.php with any other action= value means a different plugin entirely (live search, product filters, popup builders, analytics beacons). Each one needs its own diagnosis.

If you throttle Heartbeat on a site whose real problem is cart fragments, the CPU graph will not move. That is the fastest way to waste an afternoon.

Measuring the impact before you change anything

Do not tune Heartbeat blindly. Either the logs or a profiler will tell you exactly how much of the admin-ajax.php traffic is actually Heartbeat, and whether that volume justifies throttling.

Server access logs. Every web server writes a line per request. Filter for admin-ajax.php and count the ones where the body or URL contains action=heartbeat. If the ratio is below a few percent of total admin-ajax.php traffic, Heartbeat is not your problem. If it is 60% or more, throttling will make a visible difference.

Query Monitor plugin. The free Query Monitor plugin adds an admin toolbar panel that lists every AJAX action fired during the current request, every hook that ran, and how long each query took. On a page where Heartbeat fires, the panel shows the heartbeat_received path explicitly. It is the fastest way to see which specific plugins have attached handlers to Heartbeat and how much they cost per tick.

Kinsta's diagnosis guide. The Kinsta admin-ajax diagnosis article walks through the same log-based method in more detail with real GTmetrix waterfall examples. It is a useful cross-reference if your access logs are unfamiliar.

Controlling Heartbeat with code

The primary path for Heartbeat control is the heartbeat_settings filter. This is core WordPress, no plugin required, and it is the approach I recommend because it survives plugin abandonment and WordPress upgrades without a thought.

Add one of the following snippets to a site-specific plugin or to functions.php in a child theme. Do not edit a parent theme or a plugin you did not author.

Throttle to 60 seconds everywhere (including the editor). This keeps autosave and post locking working, but at a slower cadence. Most sites can tolerate this with no user complaints.

// Set the Heartbeat interval to 60 seconds across all admin contexts.
add_filter( 'heartbeat_settings', function( $settings ) {
    $settings['interval'] = 60;
    return $settings;
} );

Disable Heartbeat on the front end only. Safe to do, because core does not load it there for logged-out visitors anyway. This only affects plugins or themes that enqueue it for logged-in users browsing the site.

// Deregister the heartbeat script on front-end page loads only.
add_action( 'init', function() {
    if ( ! is_admin() ) {
        wp_deregister_script( 'heartbeat' );
    }
} );

Disable Heartbeat everywhere. This is the scorched-earth option and it has real consequences (see the next section). Only use it if you have confirmed Heartbeat is the dominant load source and the editing team can live without autosave and post locking.

// Deregister the heartbeat script globally. Breaks autosave and post locking.
add_action( 'init', function() {
    wp_deregister_script( 'heartbeat' );
}, 1 );

Context-specific throttling. The Heartbeat defaults differ between the editor (15s) and the dashboard (60s) because autosave needs the faster rate. If you want to keep the editor responsive but slow everything else, use get_current_screen() inside the filter:

// Keep the editor at 15s for responsive autosave, slow other admin screens to 120s.
add_filter( 'heartbeat_settings', function( $settings ) {
    if ( function_exists( 'get_current_screen' ) ) {
        $screen = get_current_screen();
        if ( $screen && in_array( $screen->base, array( 'post', 'post-new' ), true ) ) {
            return $settings; // Leave editor at its default 15 seconds.
        }
    }
    $settings['interval'] = 120;
    return $settings;
} );

The interval parameter is clamped to the 15-120 second range in WordPress 6.7 and earlier. Trac ticket #61960 proposes expanding this range, but at the time of writing the 15-120 bound is still in force. Setting a value outside the range is silently ignored.

Controlling Heartbeat with the Heartbeat Control plugin

There is a dedicated plugin for this: Heartbeat Control by WP Media, the same company behind WP Rocket. It exposes a UI with sliders for the dashboard, the front end, and the post editor, and lets you enable, disable, or throttle each context independently. Over 80,000 sites use it.

There is one caveat. The plugin's last release (version 2.0.1) is dated August 2023, tested only up to WordPress 6.3. As of WordPress 6.7 the WordPress.org listing shows the "this plugin has not been tested with the latest three major releases" warning. It still works for most sites because the heartbeat_settings filter it wraps has not changed, but you should treat it as a plugin on life support rather than an actively maintained dependency.

Use the plugin if you specifically need the UI (non-developer admins who want to adjust settings themselves, or agencies deploying the same configuration across many client sites). Use the code snippets above for anything else. The code approach is one file you control, with no plugin update risk and no admin UI to manage.

What breaks if you disable Heartbeat

Every reference that says "disable Heartbeat to reduce server load" skips this section. It matters, because the wrong setting will quietly break features editors depend on.

  • Autosave stops. A browser crash, a network drop, or an accidental tab close during editing will lose everything typed since the last manual save. On a long-form post, that can be thirty minutes of writing gone.
  • Post locking stops. Two editors can open the same post and each save on top of the other. The last save wins, the first editor's changes are silently overwritten, and there is no warning dialog because the locking mechanism is gone.
  • Plugin notifications stop. WooCommerce "new order" flash notices, security scan completion messages, real-time backup status, and similar plugin features that push updates to the admin rely on Heartbeat. They fail silently.
  • Page builders may misbehave. Elementor, Divi, and several other visual builders use Heartbeat for real-time editor state (e.g. revision history, collaborator indicators, autosave within the builder). Disabling Heartbeat globally can cause editor errors that look like bugs in the builder itself. Perfmatters documents this explicitly: disabling Heartbeat everywhere sometimes breaks page builder functionality.
  • The block editor loses its post lock indicator. Gutenberg uses Heartbeat for post locking the same way the classic editor did. Disabling Heartbeat breaks this in both.

Safe default. Throttle to 60 seconds in the editor and keep the dashboard at its default. This cuts editor traffic by three-quarters while preserving autosave and post locking. Only escalate to full disable if you have measured that Heartbeat is dominating admin-ajax.php traffic and the team has agreed to accept the loss of autosave.

Context-specific settings: dashboard vs. post editor

The split between 15-second editor ticks and 60-second dashboard ticks exists for a reason. Autosave matters more on the screen where the user is actively typing. That is the editor. The dashboard is an idle surface that refreshes counters, and nothing on it would be painful to lose.

If your problem is dashboard tabs left open by editors who are no longer looking at them, the right fix is to throttle the dashboard further (e.g. to 120 seconds) and leave the editor alone. Do not compromise editor autosave for dashboard background traffic: the editor is where the pain of lost work is felt, and where the cost of slower ticks is highest.

If your problem is a small number of power users leaving editor tabs open all day, the right fix is to throttle the editor to 30 or 60 seconds, because background tabs with Heartbeat slow-mode are already generating roughly half the traffic and the team can accept a slightly slower autosave feel.

If your problem is many concurrent editors all actively writing at the same time, the PHP workers article is the better reference because you are hitting worker pool limits, not Heartbeat limits. Throttling Heartbeat will help marginally; adding workers or moving to a bigger plan will help more.

When to leave Heartbeat alone

Heartbeat is not an obviously broken piece of WordPress. For most sites it consumes a fraction of a percent of server capacity and delivers features (autosave, post locking, real-time notifications) that are worth far more than the cost. There are cases where the right answer is to do nothing:

  • One-person sites. A single admin with one or two editor tabs open generates trivial Heartbeat traffic. The entire feature is invisible on the CPU graph.
  • Sites where the real problem is something else. If your access log shows wc-ajax=get_refreshed_fragments as the dominant offender, or admin-ajax.php traffic with non-heartbeat action= values, throttling Heartbeat changes nothing. Fix the real source.
  • Sites with low editor concurrency. Two or three editors, each with a single tab, are not a Heartbeat problem. They are a normal WordPress workload, and the 4 requests-per-minute budget per editor is what WordPress expects.
  • Sites where the hosting provider's complaint is about a different metric. A provider email about "high CPU" on a site with 150 KB of autoload bloat and a nightly backup scanner is not a Heartbeat story. The high CPU usage article walks through which mechanism is actually consuming the budget.

The most common waste of effort in WordPress performance work is throttling Heartbeat when the real problem lives somewhere else. Measure first, then change one thing at a time, then measure again. That is the only sequence that reliably moves the graph in the right direction.

Where to go next

If your hosting provider reported admin-ajax.php spikes and you have ruled out Heartbeat, the high CPU usage article covers the full list of mechanisms that consume CPU on a WordPress server. If the admin itself feels slow, the WordPress admin is slow article explains why every admin request is heavier than a front-end one, with Heartbeat as one of five structural causes. If you have many editors writing concurrently, the PHP workers exhausted article covers the worker pool model that Heartbeat traffic competes for.

Done chasing slowdowns?

Performance issues tend to come back after quick fixes. WordPress maintenance keeps updates, caching and limits consistent.

See WordPress maintenance

Search this site

Start typing to search, or browse the knowledge base and blog.