WP-Cron porte mal son nom : ce n’est pas un vrai cron système, mais un mécanisme déclenché à chaque visite du site via une requête vers wp-cron.php, qui vérifie si des tâches planifiées sont arrivées à échéance. Ce choix de conception, pensé pour fonctionner même sur un hébergement mutualisé sans accès à la crontab du serveur, explique à lui seul l’essentiel des problèmes rencontrés en production.
Cet article recense les pièges les plus fréquents observés dans des extensions qui utilisent wp_schedule_event(), avec pour chacun la correction concrète à appliquer.
Piège n°1 : programmer une tâche à chaque chargement
L’erreur la plus commune consiste à appeler wp_schedule_event() directement dans le corps du fichier principal de l’extension, sans vérification préalable :
// À NE PAS FAIRE : reprogramme la tâche à chaque requête
add_action( 'init', function () {
wp_schedule_event( time(), 'hourly', 'acme_verification_stock' );
} );
wp_schedule_event() ne vérifie pas par lui-même si une tâche identique existe déjà exactement de cette façon ; répéter cet appel finit par empiler des dizaines d’événements identiques dans la table d’options cron, ralentissant chaque vérification de WP-Cron. La correction :
register_activation_hook( __FILE__, function () {
if ( ! wp_next_scheduled( 'acme_verification_stock' ) ) {
wp_schedule_event( time(), 'hourly', 'acme_verification_stock' );
}
} );
register_deactivation_hook( __FILE__, function () {
$timestamp = wp_next_scheduled( 'acme_verification_stock' );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, 'acme_verification_stock' );
}
} );
La programmation a lieu une seule fois, à l’activation, et la tâche est proprement retirée à la désactivation. C’est la seule approche fiable pour éviter l’accumulation d’événements fantômes.
Piège n°2 : croire à une précision horaire
Sans visite sur le site, wp-cron.php n’est jamais déclenché. Un site à faible trafic peut voir une tâche programmée « toutes les heures » ne s’exécuter en réalité qu’une fois par jour, au moment de la première visite. C’est un problème réel pour toute logique métier sensible au temps : purge de sessions expirées, envoi d’e-mails programmés, synchronisation avec un service externe.
Sur un projet e-commerce à trafic modéré, une tâche de relance panier programmée « toutes les heures » ne se déclenchait en pratique qu’entre deux et quatre fois par jour. Le diagnostic a pris du temps car WP-Cron ne signale jamais explicitement qu’il n’a pas pu s’exécuter : il se contente de ne rien faire.
La solution recommandée en production consiste à désactiver le déclenchement par visite et à le remplacer par un vrai cron système, plus prévisible :
// Dans wp-config.php
define( 'DISABLE_WP_CRON', true );
# Dans la crontab du serveur, toutes les cinq minutes
*/5 * * * * curl -s https://exemple.fr/wp-cron.php?doing_wp_cron >/dev/null 2>&1
Attention : DISABLE_WP_CRON à true sans configuration d’un vrai cron système équivaut à désactiver purement et simplement toutes les tâches planifiées du site, y compris celles du cœur de WordPress (vérification des mises à jour, purge de la corbeille). C’est une erreur de configuration fréquente lors d’une migration d’hébergement.

Piège n°3 : des intervalles personnalisés mal déclarés
WordPress propose nativement hourly, twicedaily et daily. Un intervalle personnalisé doit être ajouté via le filtre cron_schedules, avant que wp_schedule_event() ne soit appelé, sous peine d’une erreur silencieuse qui empêche la programmation.
add_filter( 'cron_schedules', function ( $schedules ) {
$schedules['acme_toutes_les_15_minutes'] = [
'interval' => 15 * MINUTE_IN_SECONDS,
'display' => __( 'Toutes les 15 minutes', 'acme' ),
];
return $schedules;
} );
La constante MINUTE_IN_SECONDS, ainsi que HOUR_IN_SECONDS, DAY_IN_SECONDS et WEEK_IN_SECONDS, sont définies nativement par WordPress et évitent d’écrire des calculs de secondes à la main, source classique d’erreur d’un facteur dix.
Piège n°4 : un callback trop long qui dépasse le temps d’exécution
Une tâche accrochée à un hook de cron s’exécute dans le même processus PHP que la requête qui a déclenché wp-cron.php, avec les mêmes limites de max_execution_time que n’importe quelle page. Un traitement lourd (synchronisation de plusieurs milliers de produits, par exemple) peut être interrompu brutalement avant la fin, sans transaction ni retour en arrière automatique.
- Découper le traitement en lots (batches) traités sur plusieurs exécutions successives plutôt qu’en une seule passe
- Enregistrer une progression en base (via une option ou une métadonnée) pour reprendre là où le traitement s’est arrêté
- Pour des volumes importants ou des tâches déclenchées par événement plutôt que par intervalle fixe, envisager Action Scheduler plutôt que WP-Cron natif
Diagnostiquer les tâches programmées
WP-CLI reste l’outil le plus rapide pour inspecter l’état réel du cron d’un site, sans dépendre du trafic pour forcer une exécution :
wp cron event list
wp cron event run acme_verification_stock
wp cron test
La commande wp cron event run exécute immédiatement une tâche donnée, indépendamment de son échéance programmée : un outil précieux pour tester une tâche en développement sans attendre l’heure prévue.
En résumé
WP-Cron rend un vrai service sur les hébergements sans accès à la crontab, mais son fonctionnement dépendant du trafic le rend inadapté à toute tâche exigeant une précision horaire. Sur un projet en production sérieux, la combinaison DISABLE_WP_CRON plus un vrai cron système reste la configuration la plus fiable, à condition de ne jamais oublier de la mettre en place après une migration.