wp cron event list --format=count renvoyait 14 212. Sur un site associatif qui ne recevait pourtant qu’une trentaine de formulaires par jour, ce chiffre n’avait aucun sens tant qu’on n’avait pas ouvert le détail des événements planifiés un par un.
Le mécanisme de wp-cron de WordPress ne s’exécute pas en tâche de fond au sens strict : il se déclenche à chaque visite du site, via une requête discrète vers wp-cron.php, dès qu’un événement planifié est arrivé à échéance. Plus la file d’événements en attente est longue, plus WordPress passe de temps à la parcourir à chaque chargement de page, ce qui explique pourquoi un temps de génération qui se dégrade lentement, semaine après semaine, mérite qu’on aille y regarder de près.
Le symptôme : un temps de génération qui grimpe sans cause apparente
Rien dans les journaux d’erreurs ne signalait de problème. Le temps de génération de la page d’accueil, suivi via un en-tête Server-Timing personnalisé, était passé de 180 à 340 millisecondes en trois semaines, sans qu’aucune modification de code n’ait été déployée sur cette période. Le suspect naturel — une extension de sécurité récemment installée — a été écarté après désactivation temporaire : le ralentissement persistait.
Le diagnostic : une planification sans garde-fou

La commande wp cron event list a révélé des milliers d’événements portant le même nom, wpm_relance_formulaire, chacun programmé quelques minutes après une soumission de formulaire de contact. Le code responsable, ajouté par un développeur pour envoyer un rappel automatique en cas d’inscription incomplète, ressemblait à ceci :
add_action( 'wpm_formulaire_soumis', function ( $email ) {
wp_schedule_single_event(
time() + 3 * DAY_IN_SECONDS,
'wpm_relance_formulaire',
array( $email )
);
} );
Le problème saute aux yeux une fois isolé : rien n’empêchait qu’un même visiteur soumette le formulaire plusieurs fois — par une double validation accidentelle, ou en corrigeant une erreur de saisie — et chaque soumission ajoutait un nouvel événement, sans jamais vérifier qu’une relance était déjà programmée pour la même adresse. Les événements passés s’exécutaient bien, mais leur volume avait fini par alourdir sérieusement la table wp_options, où wp-cron stocke sa liste sous la clé cron sous forme de tableau sérialisé.
Le correctif : vérifier avant de planifier
La fonction wp_next_scheduled() permet précisément de vérifier qu’un événement portant les mêmes arguments n’est pas déjà en attente avant d’en ajouter un nouveau :
add_action( 'wpm_formulaire_soumis', function ( $email ) {
if ( ! wp_next_scheduled( 'wpm_relance_formulaire', array( $email ) ) ) {
wp_schedule_single_event(
time() + 3 * DAY_IN_SECONDS,
'wpm_relance_formulaire',
array( $email )
);
}
} );
Ce contrôle, ajouté en une ligne, a immédiatement stoppé l’accumulation. Restait à purger les événements déjà empilés, ce que la commande wp cron event delete a permis de faire en ciblant le hook concerné, événement par événement, avant qu’un script ponctuel ne vienne nettoyer les doublons restants directement via _get_cron_array() et _set_cron_array().
Ce que la mesure a confirmé après nettoyage
- Le temps de génération de la page d’accueil est revenu à 175 millisecondes, une valeur cohérente avec la mesure d’origine.
- La taille de l’entrée
crondanswp_optionsest passée de plus de 900 kilo-octets à moins de 4 kilo-octets. - Le déclenchement de
wp-cron.phpà chaque visite, jusque-là ralenti par la désérialisation d’un tableau volumineux, s’exécute désormais quasi instantanément.
Prévenir plutôt que guérir
Au-delà du correctif ponctuel, l’équipe a ajouté une vérification périodique : un événement planifié une fois par semaine compte désormais le nombre total d’entrées dans la file wp-cron et envoie une alerte si ce nombre dépasse un seuil raisonnable. Un simple garde-fou qui aurait permis de détecter le problème bien avant qu’il n’affecte la page d’accueil.
Planifier un événement sans vérifier son existence préalable revient à empiler des rappels sans jamais les compter : la file grossit en silence jusqu’à peser sur chaque page du site.
En résumé
Un simple oubli — l’absence de vérification avant planification — a transformé une fonctionnalité de relance en fuite silencieuse qui a fini par dégrader le temps de génération de l’ensemble du site. La correction tient en une condition, mais le diagnostic a demandé de remonter jusqu’au contenu réel de la table wp_options, là où wp-cron range sa file d’attente.