Le problème
Si vous avez déjà vu une requête admin-ajax.php qui finit en 400 ou 500 dans l’onglet Réseau de votre navigateur, vous avez probablement aussi vu un formulaire qui ne s’envoie plus, un bouton “Charger plus” qui tourne dans le vide, ou un builder (Divi/Elementor/Avada) qui refuse d’enregistrer.
Le message typique côté navigateur ressemble à ça :
POST https://exemple.com/wp-admin/admin-ajax.php 500 (Internal Server Error)
ou
POST https://exemple.com/wp-admin/admin-ajax.php 400 (Bad Request)
Où ça apparaît :
- Front-end : formulaires (newsletter, contact), filtres produits, recherche instantanée, “load more”.
- Admin : sauvegarde d’options, import/export, éditeurs visuels, médias, certaines metabox.
- Builders : Divi 5 (sauvegarde/preview), Elementor (éditeur qui boucle), Avada/Fusion (AJAX de modules et d’options).
Circonstances typiques que j’ai souvent vues en support :
- Après une mise à jour (WordPress 6.9.4, un plugin de cache, un plugin de sécurité).
- Après activation d’un plugin qui ajoute un endpoint AJAX “vite fait” sans nonce.
- Après migration d’hébergement : WAF plus strict, PHP 8.1/8.2, limites mémoire, règles ModSecurity.
- Après ajout d’un snippet dans functions.php (ou un plugin de snippets) qui echo quelque chose au mauvais moment.
À qui s’adresse ce guide : à des utilisateurs intermédiaires (vous savez lire du PHP/JS WordPress, utiliser DevTools, toucher wp-config.php). À la fin, vous saurez identifier si le 400/500 vient du code WordPress (handler AJAX), d’un conflit plugin/thème, ou du serveur, et vous aurez des correctifs concrets.
Résumé rapide
- 400 sur admin-ajax.php = souvent action manquante, nonce invalide, requête bloquée par WAF, ou payload trop gros.
- 500 = erreur PHP fatale, mémoire insuffisante, timeout, ou plugin qui casse la sortie (JSON corrompu).
- Diagnostiquez d’abord dans DevTools > Network : payload, réponse, headers, et contenu renvoyé.
- Activez des logs propres : WP_DEBUG_LOG + logs PHP/serveur, puis reproduisez une seule action AJAX.
- Pour les handlers : utilisez check_ajax_referer(), current_user_can(), et terminez avec wp_send_json_success() / wp_send_json_error().
- Si un WAF/caching intervient : testez en contournant temporairement (désactiver règles, bypass cache, whitelist admin-ajax.php).
Les symptômes
Les symptômes “visibles” varient selon le plugin qui appelle admin-ajax.php, mais les patterns reviennent.
Symptômes côté navigateur
- Dans DevTools : POST admin-ajax.php 400 ou 500.
- Réponse vide, ou réponse HTML au lieu de JSON (ex. une page d’erreur/maintenance renvoyée par un cache).
- Dans la console :
Unexpected token < in JSON at position 0(très fréquent quand PHP affiche un warning avant le JSON). - Dans la console :
ajaxurl is not defined(front-end) si un code admin est copié côté public.
Symptômes côté WordPress
- Bouton “Enregistrer” qui tourne (Elementor), “Unable to save” (Divi), ou modules qui ne se chargent plus (Avada).
- Metabox qui ne sauvegarde plus, options qui reviennent à l’état précédent.
- Erreur intermittente : ça marche pour admin, pas pour visiteurs (ou inversement).
Symptômes côté serveur
- Logs PHP :
Fatal error,Allowed memory size exhausted,Maximum execution time. - Logs Nginx/Apache :
upstream sent too big header,client intended to send too large body,ModSecurity: Access denied. - Problème “uniquement en prod” : local OK, staging OK, prod KO (souvent WAF, cache, ou limites PHP différentes).
Tableau de diagnostic rapide
| Symptôme | Cause probable | Vérification | Solution |
|---|---|---|---|
| 400 immédiat | Paramètre action manquant / vide |
DevTools > Network > Payload | Envoyer action=... et déclarer le hook wp_ajax_* |
| 400 après update sécurité | WAF / ModSecurity bloque | Logs WAF, code 403/400 en amont | Whitelist règle, bypass sur /wp-admin/admin-ajax.php |
| 500 + réponse HTML | Fatal PHP / page d’erreur | WP_DEBUG_LOG + error_log PHP | Corriger l’erreur, augmenter mémoire/timeout |
| JSON cassé | Warning/notice PHP avant JSON | Réponse contient du HTML/texte avant {"success":...} |
Supprimer echo/var_dump, corriger warnings, wp_send_json_* |
| Ça marche connecté, pas déconnecté | Hook wp_ajax_nopriv_* absent |
Tester en navigation privée | Ajouter wp_ajax_nopriv_{action} |
Pourquoi ça arrive
Version simple : admin-ajax.php est une porte d’entrée unique pour des actions AJAX WordPress. Si la requête n’est pas conforme (action/nonce), si le code plante, ou si le serveur bloque, vous obtenez un 400/500.
Voici ce qui se passe en coulisses (WordPress 6.9.4, PHP 8.1+) :
- Le navigateur fait un POST vers
/wp-admin/admin-ajax.phpavec au minimumaction. - WordPress charge l’environnement admin minimal, puis déclenche une action du type
wp_ajax_{action}(ouwp_ajax_nopriv_{action}si l’utilisateur n’est pas connecté). - Votre handler doit valider (nonce + droits), traiter, puis répondre proprement (idéalement JSON) et terminer.
Causes classées (du plus fréquent au plus rare)
- Handler AJAX mal écrit : pas de nonce, pas de nopriv, sortie polluée (echo), pas de
wp_die()/wp_send_json_*. - Erreur PHP fatale dans un plugin/thème déclenchée uniquement pendant l’AJAX (ex. fonction appelée trop tôt, classe non chargée).
- Blocage WAF (ModSecurity, Cloudflare WAF, Imunify360) sur certains patterns (payload JSON, champs “email”, “url”, “script”).
- Limites serveur : mémoire PHP, max_execution_time, upload/post size, limite Nginx/Apache sur la taille du body.
- Cache / optimisation : minification qui modifie votre JS, cache qui renvoie une page HTML à la place du JSON, compression/brotli mal négociée.
- Problème de cookies/sessions : cookies trop gros (headers), ou domaines mal configurés (www vs non-www) qui cassent l’auth.
- Règles de réécriture / proxy : plus rare sur admin-ajax.php (URL fixe), mais possible avec un reverse proxy mal configuré.
Prérequis avant de commencer
Avant de toucher au code ou au serveur, sécurisez votre diagnostic. J’ai vu trop de sites “réparés” en prod… puis cassés plus fort par un test à l’aveugle.
- Sauvegarde fichiers + base (ou snapshot si VPS).
- Staging si possible (même PHP, même WAF, même cache).
- Versions : WordPress 6.9.4, PHP recommandé 8.1+. Vérifiez dans Outils > Santé du site.
Activer des logs exploitables (sans tout casser)
Dans wp-config.php, activez les logs sans afficher les erreurs aux visiteurs :
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
/* Optionnel : évite que des notices cassent du JSON côté navigateur */
@ini_set( 'display_errors', '0' );
Le fichier wp-content/debug.log doit se remplir. Si rien ne s’écrit, vérifiez les permissions ou un réglage d’hébergement qui redirige les logs PHP ailleurs.
Outils utiles
- Query Monitor (pour voir erreurs PHP, requêtes, hooks) : https://wordpress.org/plugins/query-monitor/
- Health Check & Troubleshooting (mode dépannage sans désactiver pour les visiteurs) : https://wordpress.org/plugins/health-check/
- WP-CLI (si vous avez accès SSH) : https://developer.wordpress.org/cli/commands/
Solution 1 : corriger le handler AJAX (nonce, droits, et réponse JSON)
Quand je vois un 400/500 sur admin-ajax.php, la moitié du temps le problème est un handler “rapide” copié d’un vieux tuto. Sous WordPress 6.9.4, ça ne pardonne pas : nonces absents, hooks incomplets, et JSON pollué.
Cas typique : action manquante, pas de nopriv, et sortie cassée
Avant (cassé) : snippet souvent collé dans functions.php ou un plugin de snippets.
<?php
// Mauvais exemple : code fragile, souvent à l'origine de 400/500.
add_action( 'wp_ajax_save_rating', 'my_save_rating' );
function my_save_rating() {
// Erreur fréquente : pas de nonce, pas de droits, et on "echo" du texte.
echo "OK";
global $wpdb;
$post_id = $_POST['post_id']; // Non sanitizé
$rating = $_POST['rating']; // Non validé
// Erreur fréquente : requête SQL bricolée (risque sécurité).
$wpdb->query( "INSERT INTO {$wpdb->prefix}ratings (post_id, rating) VALUES ($post_id, $rating)" );
// Erreur fréquente : pas de wp_die(), WordPress continue et renvoie du HTML en plus.
}
Ce code peut produire :
- 400 si l’appel est fait par un utilisateur non connecté (pas de
wp_ajax_nopriv_save_rating). - 500 si la table n’existe pas, si SQL casse, ou si un warning PHP s’affiche.
- Du JSON impossible à parser (car
echo "OK"+ sortie WordPress).
Après (corrigé, robuste, compatible WP 6.9.4+)
Objectif : valider nonce + droits, nettoyer les entrées, répondre en JSON, terminer proprement.
<?php
/**
* Exemple complet et robuste d'action AJAX WordPress.
* Compatible WordPress 6.9.4+ et PHP 8.1+.
*/
/**
* Enregistrement côté connecté.
*/
add_action( 'wp_ajax_bpcab_save_rating', 'bpcab_save_rating_ajax' );
/**
* Enregistrement côté non connecté (si vous l'autorisez).
* Supprimez cette ligne si l'action doit rester réservée aux comptes.
*/
add_action( 'wp_ajax_nopriv_bpcab_save_rating', 'bpcab_save_rating_ajax' );
function bpcab_save_rating_ajax(): void {
// 1) Vérification anti-CSRF (nonce).
// Le champ envoyé doit s'appeler "nonce" (ou adaptez le 2e paramètre).
check_ajax_referer( 'bpcab_rating', 'nonce' );
// 2) Contrôle de droits (adaptez à votre besoin).
// Pour un vote public, vous pouvez retirer ce bloc, mais gardez le nonce.
if ( is_user_logged_in() && ! current_user_can( 'read' ) ) {
wp_send_json_error(
array( 'message' => 'Droits insuffisants.' ),
403
);
}
// 3) Validation/sanitization des entrées.
$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
$rating = isset( $_POST['rating'] ) ? (int) $_POST['rating'] : 0;
if ( $post_id <= 0 || $rating < 1 || $rating > 5 ) {
wp_send_json_error(
array( 'message' => 'Paramètres invalides.' ),
400
);
}
// 4) Traitement : exemple simple via meta (évite une table custom).
// Attention : sur gros trafic, prévoyez une stratégie d'agrégation.
$ok = update_post_meta( $post_id, '_bpcab_rating_last', $rating );
if ( false === $ok ) {
wp_send_json_error(
array( 'message' => 'Échec de l’enregistrement.' ),
500
);
}
// 5) Réponse JSON propre.
wp_send_json_success(
array(
'message' => 'Vote enregistré.',
'post_id' => $post_id,
'rating' => $rating,
),
200
);
}
Pourquoi ça corrige (et ce que ça évite)
- check_ajax_referer() coupe court aux requêtes sans nonce (souvent interprétées comme 400/403 selon le contexte). Doc : developer.wordpress.org/reference/functions/check_ajax_referer/
- wp_send_json_success/error() renvoie un JSON correct + termine l’exécution. Doc : developer.wordpress.org/reference/functions/wp_send_json_success/
- La validation (
absint, bornes 1..5) évite les erreurs SQL et les comportements “aléatoires”. - La sortie n’est plus polluée par des
echo,var_dump, warnings PHP, etc.
JS côté front : l’erreur “action manquante” et le nonce
Autre cause fréquente : le JS envoie bien des données, mais oublie action ou le nonce. Exemple minimal propre :
(function () {
async function sendRating(postId, rating) {
const body = new URLSearchParams();
body.set('action', 'bpcab_save_rating');
body.set('post_id', String(postId));
body.set('rating', String(rating));
body.set('nonce', window.bpcabAjax?.nonce || '');
const res = await fetch(window.bpcabAjax?.ajaxUrl || '/wp-admin/admin-ajax.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
body
});
// Si vous recevez du HTML, ce .json() va planter : c'est un signal utile.
const data = await res.json();
return data;
}
window.bpcabSendRating = sendRating;
})();
Et l’enqueue WordPress correspondant (avec nonce + ajaxUrl) :
<?php
add_action( 'wp_enqueue_scripts', function (): void {
wp_enqueue_script(
'bpcab-rating',
get_stylesheet_directory_uri() . '/assets/js/rating.js',
array(),
'1.0.0',
true
);
wp_localize_script(
'bpcab-rating',
'bpcabAjax',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'bpcab_rating' ),
)
);
} );
Doc de référence sur admin-ajax et les actions : developer.wordpress.org/plugins/javascript/ajax/
Compatibilité Divi 5 / Elementor / Avada
- Divi 5 : si vous déclenchez l’AJAX depuis un module, évitez de compter sur des variables globales “admin” (type
ajaxurl). Localisez votre script comme ci-dessus. - Elementor : beaucoup d’actions passent déjà par admin-ajax. Un JSON pollué (même un simple “notice”) casse l’éditeur. Sur Elementor, un
Unexpected token <est presque toujours une réponse HTML (fatal, WAF, maintenance, cache). - Avada/Fusion : même logique. Les optimisations JS (minify/combine) peuvent modifier l’ordre de chargement. Si l’erreur apparaît après activation d’un plugin d’optimisation, testez en désactivant la combinaison JS.
Solution 2 : trouver l’erreur 500 côté serveur (logs, WP_DEBUG, Query Monitor)
Un 500 sur admin-ajax.php n’est pas “une erreur AJAX”. C’est un crash serveur pendant l’exécution WordPress. Votre mission : trouver la première erreur (pas la dernière) dans les logs.
Étape 1 : regarder la réponse brute dans Network
Dans Chrome/Firefox DevTools :
- Ouvrez Network, filtrez sur
admin-ajax.php. - Rejouez l’action (clic, save, etc.).
- Ouvrez la requête > onglet Response.
Ce que vous cherchez :
- Une page HTML (maintenance, WAF, erreur PHP formatée par l’hébergeur).
- Un message type
Fatal error:(parfois visible directement). - Un JSON valide mais
{"success":false,"data":...}(meilleur cas : votre handler gère l’erreur).
Étape 2 : activer un log propre et reproduire une seule fois
Si WP_DEBUG_LOG est activé (section prérequis), reproduisez l’action, puis ouvrez wp-content/debug.log.
Exemples d’erreurs réelles qui provoquent un 500 :
PHP Fatal error: Uncaught Error: Call to undefined function get_field() in /wp-content/themes/mon-theme/functions.php:123
PHP Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 20480 bytes)
PHP Fatal error: Uncaught TypeError: strpos(): Argument #1 ($haystack) must be of type string, array given
Étape 3 : isoler plugin/thème sans casser le site public
Le plugin Health Check a un mode “Troubleshooting” qui désactive plugins/thème uniquement pour votre session admin. C’est souvent le moyen le plus rapide de confirmer un conflit.
- Activez le mode dépannage.
- Testez l’action AJAX qui plante.
- Réactivez les plugins un par un jusqu’à reproduire.
Si vous avez accès au back-office mais que l’AJAX casse l’admin, vous pouvez aussi passer par WP-CLI (solution 3) pour désactiver en masse.
Étape 4 : Query Monitor pour attraper les warnings qui cassent du JSON
Un cas que je rencontre souvent : pas de fatal, mais des warnings/notices qui s’impriment et rendent la réponse invalide pour res.json().
Avec Query Monitor :
- Regardez “PHP Errors”.
- Cherchez des erreurs déclenchées lors de la requête AJAX.
Si vous voyez un warning du type “Undefined array key”, corrigez-le. Sous PHP 8.1+, ces warnings sont plus stricts, et beaucoup de vieux snippets se mettent à polluer la sortie.
Exemple concret : JSON cassé par un echo/notice
Avant (cassé) :
<?php
add_action( 'wp_ajax_bpcab_ping', function () {
// Erreur : un espace ou un echo casse la réponse JSON attendue.
echo " ";
wp_send_json_success( array( 'pong' => true ) );
} );
Après (corrigé) :
<?php
add_action( 'wp_ajax_bpcab_ping', function (): void {
// Aucun echo/var_dump avant wp_send_json_*.
wp_send_json_success( array( 'pong' => true ) );
} );
Solution 3 : cas serveur (WAF, cache, HTTP/2), limites PHP et WP-CLI
Quand le code est propre mais que vous avez encore des 400/500, le coupable est souvent autour de WordPress : WAF, reverse proxy, cache, ou limites PHP/Nginx.
3.1 WAF / ModSecurity : le 400 “mystère”
Symptôme classique : admin-ajax.php renvoie 400/403 uniquement sur certains contenus (ex. un champ qui contient “<script>”, une URL, ou un JSON).
Vérifications :
- Regardez les logs WAF (Cloudflare, Sucuri, Imunify360, ModSecurity). Souvent, vous verrez un rule ID.
- Testez en changeant légèrement la valeur d’un champ (si le 400 disparaît, c’est un blocage pattern-based).
Solutions réalistes :
- Whitelist ciblée : autoriser
/wp-admin/admin-ajax.php(ou une action précise si votre WAF le permet). - Désactiver la règle ModSecurity spécifique (hébergeur managé requis).
- Réduire le payload : éviter d’envoyer du HTML brut ou de gros blobs via AJAX.
Attention sécurité : “whitelister admin-ajax.php” trop largement peut ouvrir une surface d’attaque. Faites-le seulement si vos actions sont protégées par nonce + capacités.
3.2 Cache / optimisation : HTML à la place du JSON
J’ai souvent croisé ce problème sur des sites avec cache agressif : une réponse AJAX reçoit une page de cache (HTML), ou une page “Vérification du navigateur” (anti-bot).
Vérifications :
- Dans Network > Response : cherchez
<!doctype html>ou une page de challenge. - Vérifiez les headers :
content-typedoit êtreapplication/jsonpour une réponse JSON.
Solutions :
- Exclure
/wp-admin/admin-ajax.phpdu cache de page. - Désactiver temporairement la minification/combinaison JS pour tester (surtout si un builder est concerné).
- Vider tous les caches : plugin, serveur (Varnish), CDN, et cache navigateur.
3.3 Limites PHP : mémoire et temps d’exécution
Sur admin-ajax, on voit souvent des actions trop lourdes : génération PDF, import, requêtes WP_Query énormes, appels API externes.
Vérifiez dans vos logs :
Allowed memory size exhaustedMaximum execution time of X seconds exceeded
Solutions :
- Optimiser le traitement (pagination, batch, éviter les requêtes non indexées).
- Augmenter les limites côté serveur (php.ini / panel hébergeur) si justifié.
Réglage côté WordPress (ne remplace pas la config serveur, mais aide parfois) :
<?php
// wp-config.php
define( 'WP_MEMORY_LIMIT', '256M' );
define( 'WP_MAX_MEMORY_LIMIT', '512M' );
3.4 Nginx/Apache : taille du body (POST trop gros) => 400/413
Avec certains builders, la sauvegarde envoie un payload énorme. Selon la config, vous pouvez voir un 400/413.
- Nginx :
client_max_body_size - PHP :
post_max_size,upload_max_filesize
Référence PHP officielle : php.net/manual/en/ini.core.php
3.5 WP-CLI : désactiver un plugin qui casse admin-ajax
Si l’admin est inutilisable, WP-CLI est votre ami :
# Lister les plugins
wp plugin list
# Désactiver un plugin suspect
wp plugin deactivate nom-du-plugin
# Désactiver tous les plugins (puis réactiver un par un)
wp plugin deactivate --all
Doc WP-CLI : developer.wordpress.org/cli/commands/plugin/
Vérifications après correction
Une fois votre correction appliquée, ne vous contentez pas de “ça remarche chez moi”. Testez de façon reproductible.
- DevTools : la requête admin-ajax doit revenir en 200 (ou un 4xx géré avec JSON clair).
- Response : si vous attendez du JSON, vérifiez que la réponse commence par
{"success":et que lecontent-typeest cohérent. - Mode déconnecté : test en navigation privée (utile pour vérifier
wp_ajax_nopriv_*). - Logs : plus de fatals/warnings associés à la reproduction du bug.
- Builder : test de sauvegarde (Elementor/Divi/Avada), puis rechargement de l’éditeur.
Si ça ne marche toujours pas
Quand le problème persiste, je déroule une procédure fixe. Elle évite de tourner en rond.
- Confirmez le code HTTP exact (400, 403, 413, 500, 502) et qui le renvoie (WordPress vs proxy/WAF). Regardez les headers serveur.
- Rejouez la requête hors navigateur (curl) pour éliminer une extension ou un cache navigateur :
curl -i -X POST "https://exemple.com/wp-admin/admin-ajax.php"
-H "Content-Type: application/x-www-form-urlencoded"
--data "action=bpcab_ping"
- Désactivez temporairement (en mode troubleshooting) : cache, sécurité, optimisation JS/CSS, puis testez.
- Changez de thème temporairement (Twenty Twenty-* si dispo) via Health Check. Beaucoup d’AJAX cassent à cause d’un
functions.phpqui fait du output au mauvais moment. - Inspectez les nonces : une erreur fréquente est de générer un nonce côté admin et de l’utiliser côté front sans le localiser correctement (ou de le mettre en cache).
- Vérifiez la taille des cookies : si vous avez des plugins qui ajoutent des cookies énormes, vous pouvez provoquer des erreurs de headers (plutôt 400/502 selon infra).
- Vérifiez REST vs AJAX : certains plugins basculent sur l’API REST. Un blocage REST peut être confondu avec admin-ajax. Testez
/wp-json/et la console.
Si vous suspectez un conflit de réécriture (rare pour admin-ajax), régénérez les permaliens : Réglages > Permaliens > Enregistrer. Ça ne “répare” pas admin-ajax directement, mais ça corrige parfois des redirections foireuses après migration.
Pièges et erreurs courantes
| Symptôme | Cause probable | Solution recommandée |
|---|---|---|
| 400 et rien dans debug.log | WAF/ModSecurity bloque avant PHP | Vérifier logs WAF, whitelister règle, tester via curl |
| 500 uniquement en prod | PHP différent / extensions manquantes / limites mémoire | Aligner versions, augmenter mémoire, corriger dépendances |
Unexpected token < |
Réponse HTML (fatal, cache, maintenance) | Ouvrir Response, corriger fatal, exclure admin-ajax du cache |
| Ça marche connecté, pas invité | Hook wp_ajax_nopriv_* absent |
Ajouter le hook nopriv ou forcer login |
ajaxurl is not defined |
Code admin copié sur le front | Utiliser admin_url('admin-ajax.php') via wp_localize_script |
| Erreur après avoir “collé un snippet” | Code au mauvais endroit / parenthèse manquante / hook inadapté | Revenir en arrière, valider syntaxe, mettre dans un plugin mu si besoin |
| Réponse JSON aléatoire | Echo/notice avant JSON, ou plugin qui fait du output buffering mal géré | Supprimer echo/var_dump, corriger warnings, garder wp_send_json_* |
Erreurs réalistes que je vois souvent
- Copier le code dans header.php ou un template au lieu de functions.php / plugin (ça pollue la sortie).
- Oublier un point-virgule et provoquer un 500 sur toutes les requêtes admin-ajax.
- Utiliser le mauvais hook (ex.
initau lieu dewp_ajax_*pour traiter la requête). - Nonce généré puis mis en cache par un plugin de cache de page (nonce périmé => échecs intermittents).
- Tester en production sans sauvegarde, puis “désactiver au hasard” un plugin critique.
Variante / alternative
Méthode sans code : plugin de diagnostic
- Health Check pour isoler le conflit sans impacter les visiteurs : wordpress.org/plugins/health-check/
- Query Monitor pour voir erreurs PHP et requêtes pendant l’AJAX : wordpress.org/plugins/query-monitor/
Méthode plus avancée : migrer une action admin-ajax vers l’API REST
Pour certaines fonctionnalités front-end, l’API REST est plus simple à sécuriser et à déboguer (codes HTTP plus explicites, outils). Ce n’est pas une “réparation” d’admin-ajax, mais une alternative quand vous contrôlez le code.
Doc REST API : developer.wordpress.org/rest-api/
Éviter ce problème à l’avenir
- Standardisez vos handlers : nonce + capacités + validation +
wp_send_json_*. Pas d’exception “juste pour un test”. - Interdisez
var_dump()/echoen prod dans les handlers AJAX. Si vous devez logger, utilisezerror_log()(et un système de logs). - Évitez les actions lourdes en une requête. Faites du batch (plusieurs appels) ou une tâche planifiée.
- Surveillez les erreurs PHP après chaque mise à jour. PHP 8.1+ rend visibles des problèmes qui restaient silencieux.
- Cache : excluez explicitement admin-ajax du cache de page, et documentez-le dans votre runbook.
Petit garde-fou : refuser les actions inconnues (optionnel)
Quand vous avez beaucoup d’appels AJAX, vous pouvez loguer les actions inconnues (utile pour repérer du spam ou un plugin cassé). Attention : ne bloquez pas aveuglément si vous utilisez des plugins qui ajoutent leurs propres actions.
<?php
add_action( 'admin_init', function (): void {
// Exemple : rien à exécuter ici, c'est un rappel.
// Évitez de mettre des "die()" globaux qui casseraient admin-ajax.
} );
Je laisse volontairement ce garde-fou minimal : j’ai vu des snippets “anti-spam AJAX” casser Elementor/Divi parce qu’ils filtraient trop large.
Ressources
- AJAX dans WordPress (admin-ajax) : https://developer.wordpress.org/plugins/javascript/ajax/
check_ajax_referer(): https://developer.wordpress.org/reference/functions/check_ajax_referer/wp_send_json_success(): https://developer.wordpress.org/reference/functions/wp_send_json_success/- Query Monitor (plugin officiel) : https://wordpress.org/plugins/query-monitor/
- Health Check & Troubleshooting : https://wordpress.org/plugins/health-check/
- WP-CLI commands : https://developer.wordpress.org/cli/commands/
- Réglages ini PHP (mémoire/temps/post size) : https://www.php.net/manual/en/ini.core.php
- Code source WordPress (référence, utile pour vérifier le flux admin-ajax) : https://github.com/WordPress/wordpress-develop
Questions fréquentes
Pourquoi j’ai un 400 sur admin-ajax.php sans rien dans debug.log ?
Parce que la requête n’atteint souvent pas PHP : WAF, proxy, limite de taille, ou règle serveur. Vérifiez la réponse brute (HTML de blocage), puis les logs WAF/serveur.
Pourquoi ça marche quand je suis connecté, mais pas en navigation privée ?
Votre action est probablement enregistrée uniquement via wp_ajax_{action} et pas via wp_ajax_nopriv_{action}. Ajoutez le hook nopriv si l’action doit être publique, ou forcez l’authentification.
J’obtiens Unexpected token < côté JS : ça veut dire quoi exactement ?
Votre JS essaie de parser du JSON, mais reçoit du HTML (souvent une page d’erreur 500, une page de cache, ou une page de challenge WAF). Ouvrez l’onglet Response et corrigez la source HTML renvoyée.
Elementor/Divi/Avada affiche “Impossible d’enregistrer” : c’est admin-ajax ?
Souvent oui, mais pas toujours. Les builders utilisent un mélange d’AJAX admin-ajax et d’API REST. Regardez Network : si la requête en échec est admin-ajax.php, ce guide s’applique. Sinon, testez aussi /wp-json/.
Est-ce que je peux “désactiver admin-ajax” pour des raisons de performance ?
Non, pas raisonnablement : trop de plugins et l’admin en dépendent. Le bon levier est de corriger les actions lourdes, d’ajouter du cache applicatif, ou de migrer certains usages front-end vers REST.
Un plugin de cache peut-il vraiment casser admin-ajax ?
Oui, si le cache de page ou un proxy renvoie une page HTML à la place d’une réponse JSON, ou si la minification casse votre JS. Excluez admin-ajax du cache de page et testez sans combinaison/minification.
Est-ce dangereux de whitelister /wp-admin/admin-ajax.php dans un WAF ?
Ça peut l’être si vous avez des actions non protégées (pas de nonce, pas de contrôle de capacités). Avant toute whitelist, auditez vos actions AJAX et sécurisez-les.
Je vois un 500 mais seulement sur une action précise. Comment trouver le handler ?
Dans Network, regardez la valeur du paramètre action. Le handler est attaché à wp_ajax_{action} (et/ou wp_ajax_nopriv_{action}). Cherchez ce nom dans votre codebase (ou via “String locator” si vous l’utilisez).
Le problème apparaît après une mise à jour PHP 8.1 : pourquoi ?
PHP 8.1+ rend plus visibles des erreurs (types, clés de tableau, dépréciations) qui pouvaient passer inaperçues. Sur admin-ajax, le moindre warning peut casser une réponse JSON. Corrigez les warnings à la source.