# Your Editor Tab Is Pinging the Server All Day: A WordPress Heartbeat API Guide

The last time we got pulled into a mystery CPU complaint, the site wasn't under attack and nothing was hacked. The access log was just wall-to-wall POSTs to `admin-ajax.php`, thousands of them, all day, all from the office IP. The owner had assumed a plugin was phoning home. What was actually happening is that four people spent their day with a post open in the WordPress editor, tabbing in and out of it between other work, and every one of those tabs was doing exactly what WordPress designed it to do.

That's the Heartbeat API. It's been in core since WordPress 3.6, it runs on nearly every screen in wp-admin, and it's the reason your server sees steady background traffic from a browser tab that looks completely idle. It's also the single most misdiagnosed line in a WordPress access log, because it wears the same filename as every other AJAX call on the site.

Most advice about it is one of two extremes: leave it alone forever, or install something to kill it outright. Both are wrong often enough to be worth understanding properly. Heartbeat isn't a bug you turn off, it's a budget you set, and core gives you a real filter to set it with.

---

## What the Heartbeat API actually is

The Heartbeat API is a polling loop that runs in your browser. A JavaScript file, `wp-includes/js/heartbeat.js`, wakes up on a timer, POSTs to `admin-ajax.php` with an `action` of `heartbeat`, and hands whatever comes back to any code that asked to listen. That's the whole mechanism. It's a scheduled conversation between one open tab and your server.

### Two background systems, two completely different triggers

People conflate Heartbeat with WP-Cron constantly, and the confusion causes real misdiagnosis, because the two are triggered by opposite things.

WP-Cron fires on *visitor* traffic. Someone loads a page, WordPress checks whether anything is overdue, and runs it. No visitors means no cron, which is why scheduled posts miss their publish time on a quiet site. Heartbeat is the inverse: it fires from an *open browser tab* belonging to somebody who is already on your site, usually you. A site with no visitors and one open dashboard tab has no cron running but a steady heartbeat for as long as somebody is at the keyboard, and an open editor tab keeps beating well after they have wandered off.

They also live in different places. Cron jobs are rows in your database waiting for a trigger. Heartbeat keeps nothing on the server, no queue and no rows, it's just a timer in a browser that's currently open. Close the tab and it's gone, with nothing left behind to catch up on later.

### What rides on it

**Heartbeat is plumbing, not a feature.** On its own it does nothing you'd notice. Its value is that other things hook into an existing timer instead of each starting their own. In core, that includes post locking (the "this post is currently being edited by" warning), autosave in the block editor, session expiry detection that throws up the login overlay instead of silently eating your work, and nonce refreshing so a long editing session doesn't fail on submit.

Plugins pile on top of that. Live notification counts, order dashboards that update themselves, form builders showing new submissions, backup and migration tools reporting progress on a long job. Anything in wp-admin that updates without you refreshing is either using Heartbeat or running its own polling or push connection, and the ones that rolled their own polling are usually the heavier option.

**WHY IT MATTERS**

Every Heartbeat tick is a real, uncached PHP request that boots WordPress from scratch. It can't be served by page caching either: these are POSTs, and every mainstream caching plugin and host configuration bypasses admin-ajax.php as a matter of course. On shared hosting with a request or CPU allowance, a few tabs left open in the block editor, which core exempts from the ten-minute idle suspend, can consume a meaningful slice of your budget while producing nothing a visitor would ever see.

## The intervals nobody documents in one place

Heartbeat doesn't run at one speed. It runs at whichever speed the current screen, your recent activity, and whether the tab is visible all add up to. Here's what core actually sets, straight from `heartbeat.js`.

### Sixty seconds is the default, ten is the editor

The baseline is `mainInterval: 60` — one request per minute per open screen. That's what you get on a plain dashboard, a post list, or the Customizer.

The block editor is the loud one. When you open a post for editing, core runs `wp.heartbeat.interval( 10 )`, dropping to one request every ten seconds. That's a deliberate trade: post locking is only useful if it notices a collision quickly, and a ten-second window is short enough that two editors don't get far into the same post before one of them is warned. It also means an open editor tab generates six times the traffic of an open dashboard.

There's a temporary fast mode too. Calling `wp.heartbeat.interval( 'fast' )` sets a five-second interval, and it isn't permanent — it counts down a number of ticks (thirty by default, and thirty is also the maximum) and then reverts to whatever the interval was before. Plugins use it for a burst of responsiveness during something short, like watching an import finish.

### What happens when you switch tabs

Core uses the Page Visibility API here, and it's the part that saves most sites from their own users. When the tab loses visibility, core forces the interval to 120 seconds and it doesn't matter what it was before. Your ten-second editor tab becomes a two-minute one the moment you switch to your email.

Read that override in both directions, because it cuts both ways. It slows a fast editor tab down, but it also speeds up anything you've configured slower than 120 seconds, so a filter asking for an hour still polls every two minutes whenever the tab is hidden. There's a separate setting covered further down that's the only thing which can hold it slower than that.

That number is chosen, not arbitrary. Post locks expire after 150 seconds, so a 120-second poll leaves comfortable headroom to refresh a lock before it lapses. Push much past that and you're not just saving requests, you're risking the lock, which matters for the minimal-interval floor discussed below.

### When it stops entirely

There's a step before that, and core's own comment spells it out: after five minutes with no mouse or keyboard activity, the interval drops to 120 seconds even when the window is fully visible and focused. Core simply treats an untouched tab as a background one.

Then Heartbeat suspends itself. After ten minutes without mouse, keyboard, or touch activity, it stops on most screens, and after sixty minutes of inactivity it stops on every screen, including the editor screens that opt out of the ten-minute rule. That hard stop is what releases the post lock so a colleague isn't locked out of a post by a laptop somebody closed on Friday.

The ten-minute rule has one carve-out worth knowing. Core hooks a function called `wp_heartbeat_set_suspension` that disables the early suspend specifically on `post.php` and `post-new.php`. On editor screens, ten minutes of you staring at the draft without touching anything won't stop the heartbeat. That's intentional: it keeps your lock and your autosave alive while you're thinking. It's also why an open editor tab is the worst offender in your logs, and why "we closed the dashboards" doesn't fix the problem if everyone's parked in a draft.

**INTERVAL QUICK REFERENCE**

Default on any screen: 60 seconds. Block editor with a post open: 10 seconds. Temporary fast mode: 5 seconds, for up to 30 ticks. Tab hidden or window unfocused: 120 seconds. Five minutes with no mouse or keyboard activity even while visible: also 120 seconds. Suspends after 10 minutes idle on most screens, and after 60 minutes idle everywhere. The main interval is clamped to a range of 1 to 3600 seconds; the separate minimal-interval floor accepts up to 600.

## Why admin-ajax.php dominates your access log

Do the arithmetic on the default and it stops looking mysterious. One admin screen at 60 seconds is 60 requests an hour, or 480 across an eight-hour day. Three staff members with a tab each is 1,440 requests a day that no visitor generated. Swap one of those for an open block editor at 10 seconds and that one tab contributes 2,880 a day on its own, taking the group to 3,840.

None of that is a bug, and on decent hosting none of it is a problem either. It becomes one on cheap shared plans that meter requests, on hosts with aggressive CPU throttling, or when a plugin is doing something expensive on every tick — a poorly written notification counter running an uncached query against a large orders table will show up as "the server is slow" long before anyone thinks to look at a background poll.

### It also runs for logged-out users

This is the part that surprises people. Core registers `wp_ajax_nopriv_heartbeat`, so the endpoint answers requests from visitors who aren't logged in at all, and there's a parallel set of hooks specifically for that case. Core itself uses it for the login-expiry check on the front end.

Core doesn't enqueue the script for anonymous front-end visitors, so on a stock install this stays dormant. But a plugin can enqueue `heartbeat` on the front end, and if it does, you now have every visitor polling your server on a timer. If your admin-ajax traffic looks far too large for your team size, check whether something is running Heartbeat on the public side of the site.

### Which admin screens actually load it

Core explicitly enqueues the heartbeat script on the Customizer, the block editor, and the post list screen. That undersells its reach considerably, because two other scripts declare `heartbeat` as a dependency: `autosave`, and `wp-auth-check`, which core loads on `admin_enqueue_scripts` for every admin screen except a short exclusion list of update and network pages. Iframe requests are skipped too, and a plugin can switch the whole check off through the `wp_auth_check_load` filter.

The practical effect is that Heartbeat is loaded on essentially all of wp-admin, pulled in by the session-expiry check rather than by anything you'd guess from a list of enqueues. It's a good reminder to check dependency chains before concluding a script isn't loading somewhere.

## How to change it without breaking anything

Core exposes a filter called `heartbeat_settings`, which is applied to the settings array before it's handed to JavaScript. That's the supported way in, and it's the mechanism the "heartbeat control" plugins use under the hood.

### The filter, with the case you actually want

Slowing everything down globally is the blunt version and usually the wrong one, since it drags the editor's post locking down with it. Targeting the screen is better:

```
add_filter( 'heartbeat_settings', function ( $settings ) {
    // Leave post.php and post-new.php alone: locking and autosave need the speed.
    if ( isset( $GLOBALS['pagenow'] )
        && in_array( $GLOBALS['pagenow'], array( 'post.php', 'post-new.php' ), true ) ) {
        return $settings;
    }

    $settings['interval'] = 120; // Everywhere else: once every two minutes.

    return $settings;
} );
```

Drop that in a small site-specific plugin rather than your theme's functions.php. A theme switch would leave the filter behind, and this is infrastructure behaviour, not presentation — it has no business living in a theme.

### The clamps core enforces on you

**Your value is not taken on faith.** On the `heartbeat_settings` path, an `interval` below 1 is raised to 1 and anything above 3600 is capped at 3600. So the slowest Heartbeat you can request through the filter is one tick an hour, and asking for a day gets you an hour.

There's a second setting, `minimalInterval`, that acts as a floor. It overrides any shorter interval, including the ones core sets for the editor, which makes it the right tool when a host simply cannot absorb frequent requests. It accepts values from 1 to 600 seconds, and unlike the main interval it's locked in at initialisation and can't be changed afterwards from JavaScript or from a later response. Get the number right, because anything outside that range isn't clamped to the nearest limit, it's discarded and the floor switches off entirely. Set it once, deliberately.

### What breaks if you push it too far

Core's own source flags the boundary: setting the minimal interval longer than 120 seconds will limit or disable some functionality, post locks first among them. That's the 150-second lock expiry mentioned earlier with a safety margin built in. Poll slower than the lock lives and the lock lapses between ticks, so two people can open the same post without either being warned.

Autosave is worth separating out here, because it's commonly blamed on Heartbeat and isn't the same dial. Autosave frequency is governed by the `AUTOSAVE_INTERVAL` constant, which defaults to 60 seconds. Autosave rides the heartbeat connection to send its data, so slowing the heartbeat can delay a save, but if what you want is fewer autosaves you should change that constant instead of throttling every other thing on the tick.

**BEFORE YOU DISABLE IT ENTIRELY**

Dequeuing the heartbeat script switches off post locking, block editor autosave, session-expiry warnings and nonce refreshing all at once, plus every plugin feature quietly relying on the same tick. On a one-person site that may genuinely be an acceptable trade. On any site where two people can open the same post, it means losing work to a silent overwrite, with nothing in the interface to tell you it happened. Slowing it down is almost always the better answer than turning it off.

## Using it instead of fighting it

If you're building something that needs the server checked periodically, Heartbeat is a free ride. You get an existing timer, an existing nonce, and an existing request you're already paying for. Rolling your own `setInterval` loop against a custom endpoint means a second stream of traffic doing the same job worse.

### The hooks

Core gives you seven hooks in the `heartbeat_` family. One configures the loop, and the other six handle the request itself, split into a logged-in trio and a logged-out trio that mirror each other:

- `heartbeat_settings` — filter the settings array before it reaches the browser. This is the interval dial.
- `heartbeat_received` — read what the browser sent you and add to the response.
- `heartbeat_send` — add to the response regardless of whether the browser sent anything.
- `heartbeat_tick` — an action that fires on every tick, for work with no response payload.
- `heartbeat_nopriv_received`, `heartbeat_nopriv_send`, `heartbeat_nopriv_tick` — the same three for logged-out requests.

That family isn't quite the whole request, mind you. The logged-in handler also fires a `wp_refresh_nonces` filter that has no logged-out counterpart, which is how core pushes fresh nonces back to an open editor.

On the browser side you enqueue data with `wp.heartbeat.enqueue()` and listen for the reply on the `heartbeat-tick` jQuery event. The naming trips people up: `received` is where you read what the browser sent, `send` is where you add what it's about to get back. They're named from the server's point of view, not the browser's.

### Keep your callback cheap

Whatever you hang on these hooks runs on every tick for every open tab, so treat the cost as multiplied rather than fixed. An expensive uncached query is fine once and ruinous 360 times an hour. If a callback has to do real work, cache the result in a transient and let the heartbeat read the cached value — the tick is for delivering an answer, not for computing one from scratch each time.

## Diagnosing it on a live site

Confirming Heartbeat as the source takes about a minute. Open your browser's network tab on any admin screen, filter to `admin-ajax.php`, and wait. You'll see a POST appear on a rhythm, and the request payload will carry an `action` of `heartbeat`. Time the gap between two of them and you've measured your live interval, including any plugin quietly changing it.

To find out what's riding along, look at the response body rather than the request. Core's own keys are recognisable, and anything else is a plugin, usually named clearly enough to identify. If one plugin's payload is disproportionately large or a tick is consistently slow, you've found the thing to fix — and the fix is that plugin's behaviour, not the heartbeat carrying it.

From the server side, filter your access log to POSTs hitting admin-ajax.php and group them by source address. A steady, evenly spaced pattern from a small number of addresses during working hours is Heartbeat. Irregular bursts from many addresses, or traffic continuing at three in the morning, is something else and deserves a closer look.

## Frequently Asked Questions

Is the WordPress Heartbeat API the same thing as WP-Cron?No. They are separate systems with different triggers. WP-Cron runs scheduled tasks and is triggered by visitor page loads, so it stalls on a site with no traffic. The Heartbeat API is a JavaScript timer in an open browser tab that posts to admin-ajax.php on an interval, so it runs whenever somebody has a screen open, even if the site has no other visitors at all.

How often does the WordPress Heartbeat API run by default?The default interval in WordPress core is 60 seconds. When you open a post in the block editor, core lowers it to 10 seconds so post locking reacts quickly. If the tab loses visibility the interval is forced to 120 seconds, and the same throttle applies after five minutes with no mouse or keyboard activity even while the window is visible. The loop then suspends itself after 10 minutes of inactivity on most screens and after 60 minutes of inactivity everywhere.

Should I disable the Heartbeat API to reduce server load?Slowing it down is usually the better option. Disabling it entirely switches off post locking, block editor autosave, session-expiry warnings and nonce refreshing, along with any plugin feature that depends on the same tick. A safer approach is to use the heartbeat\_settings filter to raise the interval on ordinary admin screens while leaving the editor screens at their normal speed.

What is the slowest interval WordPress will accept?Core clamps the interval passed through the heartbeat\_settings filter to a range of 1 to 3600 seconds, so one tick per hour is the slowest value you can request that way. There is a separate minimal interval setting that acts as a floor and accepts values from 1 to 600 seconds, and a value outside that range switches the floor off rather than capping it. Core notes that setting that floor longer than 120 seconds will limit or disable some functionality, because post locks expire after 150 seconds.

Why do I see so many admin-ajax.php requests in my access log?Heartbeat is the most common source. One open admin screen at the default interval produces 60 requests an hour, and an open block editor produces 360. Several people leaving editor tabs open all day adds up quickly, and none of it can be served from page cache, because these are POST requests and mainstream caching plugins and host configurations bypass admin-ajax.php as a matter of course. Open your browser network tab, filter to admin-ajax.php, and check whether the request payload names the heartbeat action.

Does the Heartbeat API run for visitors who are not logged in?The endpoint supports it. Core registers a no-privilege heartbeat action and provides a matching set of logged-out hooks. However, core does not enqueue the heartbeat script for anonymous front-end visitors, so on a stock install nothing polls from the public side of the site. A plugin can enqueue it on the front end, which would make every visitor poll your server on a timer, so it is worth checking if your admin-ajax traffic looks far larger than your team size explains.

If your host is complaining about CPU and nobody can explain the traffic, the answer is usually sitting in an open browser tab. We're happy to take a look at the logs and tell you which of it is actually costing you something.

Built by [amplifi.studio](https://amplifi.studio) — see also [Why Your Scheduled WordPress Posts Keep Missing Their Publish Time](https://amplifi.studio/wordpress-wp-cron-explained-guide/).