POST /wp-admin/admin-ajax.php dari beberapa IP yang sama, datang dengan jarak waktu yang teratur. Biasanya pelakunya bukan serangan. Penyebabnya Heartbeat API WordPress yang terus berjalan karena ada beberapa tab dashboard atau editor yang dibiarkan terbuka.
Di support, pola ini sering bikin bingung. Pemilik situs merasa tidak sedang melakukan apa-apa, karena tab admin cuma terbuka di belakang. Padahal setiap tab itu tetap mengirim request ke server sepanjang hari.
Apa yang sebenarnya dikirim Heartbeat setiap beberapa detik
wp-includes/js/heartbeat.js dimuat di halaman admin dan menjalankan timer di browser. Setiap kali timer habis, browser mengirim POST ke admin-ajax.php dengan parameter action=heartbeat. Request ini juga membawa _nonce, screen_id (layar admin yang sedang dibuka), interval, has_focus, dan objek data berisi titipan dari fitur lain.
Bagian data itulah yang membuat Heartbeat berguna. Beberapa fitur inti menitipkan data di sana:
- Post locking: memberi tahu server bahwa kamu masih memegang post ini, supaya editor lain melihat peringatan sedang diedit.
- Autosave di classic editor: isi draft ikut dikirim lewat Heartbeat.
- Refresh nonce dan cek sesi login: kalau sesi habis, muncul popup login, jadi tulisan yang belum disimpan tidak hilang.
heartbeat_received di sisi server. Banyak plugin memakai jalur ini untuk notifikasi, statistik live, atau sinkronisasi.
Intervalnya tidak sama di semua halaman. Di banyak versi WordPress, halaman admin umum memakai interval sekitar 60 detik. Layar edit post lebih rapat, biasanya 15 detik, karena post lock dan autosave butuh update yang lebih sering. Saat tab kehilangan fokus, interval biasanya diperlambat. Kalau user lama tidak aktif, Heartbeat bisa ter-suspend. Detail ambang waktunya berbeda antar versi, jadi kalau perlu angka pasti, cek source heartbeat.js di instalasi kamu.
Kenapa satu request kecil bisa berat di sisi server
admin-ajax.php tidak ringan. Untuk tiap request, PHP memuat wp-load.php, konfigurasi, koneksi database, semua plugin aktif, dan functions.php theme. Karena konstanta WP_ADMIN bernilai true, hook admin_init juga ikut jalan. Artinya, plugin yang melakukan pengecekan lisensi, query berat, atau panggilan ke API luar di admin_init akan menjalankan semua itu lagi setiap kali Heartbeat berdetak.
Ada tiga hal lain yang memperparah:
- Request ini tidak bisa dibantu page cache. Methodnya POST dan user-nya sedang login, jadi cache halaman di level LiteSpeed, Nginx, atau plugin akan melewatkannya.
- Setiap request memakai satu PHP worker selama diproses. Di shared hosting dengan limit proses per akun, worker yang sibuk melayani Heartbeat tidak bisa melayani pengunjung.
- Jumlahnya berlipat. Sebagai contoh ilustrasi, lima tab editor dengan interval 15 detik sama dengan 20 request per menit dari satu orang. Kalau ada tiga penulis yang melakukan hal yang sama, angkanya jadi 60 request per menit. Semua request itu memuat WordPress penuh.
Memastikan lewat access log
action=heartbeat di sana. Yang bisa dilihat adalah polanya: siapa yang paling sering memanggil admin-ajax.php, dan apakah jaraknya teratur. Lokasi log tergantung setup. Di cPanel biasanya ada di domlogs atau folder access-logs milik akun, sedangkan di Nginx atau Apache polos ada di /var/log/. Sesuaikan path di contoh berikut.
BASH
# Hitung IP yang paling sering POST ke admin-ajax.php (format log combined)
grep 'POST /wp-admin/admin-ajax.php' /path/ke/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -n 10
# Lihat timestamp request dari satu IP untuk mengecek apakah jaraknya teratur
grep '203.0.113.10' /path/ke/access.log | grep 'admin-ajax.php' | awk '{print $4}' | tail -n 20admin-ajax.php datang dari pengunjung yang tidak login, cek apakah ada plugin, theme, atau page builder yang memuatnya di halaman depan.
Memastikan lewat DevTools browser
admin-ajax di kolom filter. Biarkan satu atau dua menit.
Klik salah satu request, lalu buka bagian Payload atau Request. Request Heartbeat akan menampilkan action: heartbeat, beserta screen_id dan interval. Kolom waktu di daftar request langsung menunjukkan iramanya. Buka juga tab Response untuk melihat data apa saja yang dikembalikan server. Dari situ kelihatan plugin mana yang ikut menumpang.
Perhatikan juga kolom durasi. Request Heartbeat yang butuh waktu lama untuk payload sekecil itu menandakan beban ada di bootstrap WordPress, bukan di Heartbeat. Kalau begitu, memperlambat interval hanya mengurangi gejala. Plugin yang berat di admin_init tetap perlu dicari, misalnya lewat PHP-FPM slow log atau dengan menonaktifkan plugin satu per satu di staging.
Kalau ternyata yang ramai adalah admin-ajax.php dengan action lain, berarti penyebabnya bukan Heartbeat. Catat nama action-nya, karena biasanya nama itu langsung menunjuk ke plugin tertentu.
Membatasi interval tanpa mematikan autosave dan post locking
wp_deregister_script('heartbeat'). Cara itu memang menurunkan request, tapi ikut mematikan post locking, autosave di classic editor, refresh nonce, dan peringatan sesi habis. Akibatnya, dua penulis bisa menimpa tulisan satu sama lain tanpa peringatan. Draft panjang juga bisa hilang saat sesi habis. Di block editor, autosave memakai REST API, tapi post lock dan cek sesi tetap bergantung pada Heartbeat. Jadi mematikannya tetap ada harganya.
Pendekatan yang lebih aman adalah memperlambat interval lewat filter heartbeat_settings. Taruh di mu-plugin supaya tidak ikut hilang saat ganti theme:
PHP
<?php
// wp-content/mu-plugins/heartbeat-interval.php
// Perlambat Heartbeat ke 60 detik di semua layar admin.
add_filter( 'heartbeat_settings', function ( $settings ) {
$settings['interval'] = 60;
return $settings;
} );wp_check_post_lock_window. Selama interval Heartbeat di bawah angka itu, lock tetap terjaga. Kalau interval melewatinya, editor lain bisa mengira post sudah bebas. Selain itu, filter interval di banyak versi hanya menerima rentang tertentu, umumnya 15 sampai 120 detik, jadi nilai di luar rentang bisa dibulatkan. Untuk classic editor, ingat juga bahwa autosave ikut melambat sesuai interval ini.
Kalau mau lebih selektif, kamu bisa menonaktifkan Heartbeat hanya di layar yang tidak membutuhkannya, misalnya dashboard utama. Caranya dengan mengecek layar aktif sebelum dequeue script, sambil membiarkan layar edit post tetap jalan. Plugin pengatur Heartbeat juga bisa dipakai kalau pemilik situs lebih nyaman lewat UI. Prinsipnya sama: jangan matikan di layar editor.
Di luar konfigurasi, ada kebiasaan yang sering paling efektif: tutup tab admin yang tidak dipakai. Ini terdengar sepele, tapi bagi pemilik situs dengan banyak penulis, mengedukasi tim kadang lebih berdampak daripada mengubah kode.
Jadi, kalau CPU naik dan log dipenuhi POST ke admin-ajax.php, cocokkan dulu irama request di log dengan payload di DevTools sebelum menyimpulkan apa pun. Kalau memang Heartbeat, perlambat intervalnya dan biarkan tetap di bawah jendela post lock, jangan langsung dimatikan. Kalau tiap request tetap lambat setelah itu, masalah sebenarnya ada di plugin yang berat saat WordPress di-bootstrap, dan di situlah investigasi berikutnya perlu dilakukan.