WP-Cron ne fonctionne pas : pourquoi les publications programmées échouent et comment réparer

Cet article vous a aidé ?

Si vous avez déjà vu un article « programmé » WordPress dépasser son heure de publication comme s’il avait raté une invitation à une réunion, vous avez rencontré WP-Cron dans son habitat naturel : « tentative au mieux ». Parfois il fonctionne pendant des mois. Puis le trafic diminue, un cache change de comportement, ou PHP-FPM devient grognon — et soudain votre calendrier éditorial est une imposture.

Ce n’est pas un mystère WordPress. C’est un système de planification bâti sur des requêtes web, exécuté dans une pile qui adore optimiser tout ce dont elle a besoin : un déclencheur réel et fiable. Rendons ça ennuyeux à nouveau.

WP-Cron n’est pas cron (et c’est tout le problème)

Le cron Linux est un planificateur. Il s’exécute indépendamment du trafic de votre site. WP-Cron est un planificateur au niveau de l’application qui s’exécute parce que votre site reçoit des requêtes. Cette seule différence explique la plupart des incidents de « calendrier manqué ».

WP-Cron se déclenche lorsque WordPress reçoit une requête et constate que des tâches programmées sont dues. Il effectue alors une requête HTTP « loopback » vers wp-cron.php (ou l’exécute en ligne, selon la configuration et le runtime). En d’autres termes, WordPress programme du travail, mais il ne possède pas l’horloge. Vos visiteurs la possèdent.

Quand le trafic est stable et que la pile est permissive, WP-Cron est « acceptable ». Quand le trafic est bas, ou que les requêtes sont mises en cache, ou que les loopbacks sont bloqués, WP-Cron ne se déclenche pas. Les publications programmées ne sont pas publiées. Les abonnements WooCommerce ne se renouvellent pas. Les scans de sécurité ne s’exécutent pas. Et vous découvrez de quoi dépend réellement votre activité.

Il y a aussi un problème de fiabilité ici : WP-Cron vit dans PHP, derrière des serveurs web, des caches, des WAF et des timeouts. Si votre exécuteur de cron est couplé au même chemin qui sert les utilisateurs finaux, vous avez conçu un planificateur qui peut être perturbé par… le fait de servir des utilisateurs finaux.

Vérité sèche : si la publication programmée est importante, vous voulez un vrai planificateur (cron système ou un job runner) invoquant WordPress selon un cadence connue. WP-Cron est une solution de secours. Traitez-le ainsi.

Blague #1 : WP-Cron, c’est comme un minuteur de cuisine qui ne sonne que si quelqu’un ouvre le frigo.

Comment ça échoue en production : symptômes visibles

Les pannes n’apparaissent rarement comme une alerte nette « cron cassé ». Elles apparaissent comme des bizarreries visibles par le métier. L’astuce consiste à mapper rapidement les symptômes aux modes de défaillance.

Symptômes classiques

  • Articles programmés bloqués sur « Scheduled » ou « Missed schedule ». L’heure de publication passe ; rien ne se produit.
  • La file « Scheduled Actions » de WooCommerce grossit. Abonnements, e-mails de paniers abandonnés, synchronisations de stock, webhooks — retardés en silence.
  • Les sauvegardes ne s’exécutent pas. Les plugins qui dépendent de WP-Cron ne disposent pas de temps CPU.
  • Mises à jour de l’index de recherche / sitemap en retard. Les outils SEO reposent sur des jobs programmés ; vous obtenez des résultats obsolètes.
  • Bursts aléatoires de travaux en arrière-plan. Un pic de trafic déclenche un arriéré, provoquant des requêtes lentes et des timeouts.

Ce qui se passe généralement sous le capot

  • Requête loopback bloquée ou cassée. WordPress ne peut pas s’appeler lui-même à cause du DNS, du pare-feu, d’un WAF, d’auth, TLS ou de redirections.
  • Les requêtes n’atteignent jamais WordPress. Un cache de page complète sert les réponses sans toucher PHP, donc WP-Cron ne reçoit jamais le « tic ».
  • Épuisement des workers PHP. Même si déclenché, le cron ne peut s’exécuter parce que les workers FPM sont occupés.
  • Timeouts. Le cron démarre, se fait tuer, laisse un verrou/transient, et rien ne s’exécute jusqu’à l’expiration du verrou.
  • Problèmes d’horloge. Dérive horaire ou réglages de fuseau différents qui brouillent la planification.

Guide de diagnostic rapide

Voici l’ordre dans lequel je vérifie les choses quand le métier dit « les publications programmées n’ont pas été publiées » et que je veux une réponse avant le prochain standup.

Première étape : WordPress essaie-t-il de planifier des événements ?

  1. Lister les événements cron dus avec WP-CLI.
  2. Si la file est vide, le problème peut venir de la logique de planification du plugin/thème — ou d’une confusion de fuseau.
  3. Si la file est remplie d’événements en retard, le runner n’exécute pas.

Deuxième étape : le site peut-il se rappeler lui-même ?

  1. Depuis le serveur, faire un curl sur wp-cron.php et vérifier le code HTTP et la latence.
  2. Depuis WordPress lui-même, les tests de loopback peuvent échouer à cause du DNS ou d’un egress bloqué.
  3. Rechercher des redirections vers la page de connexion, des problèmes HTTPS forcés, ou des challenges WAF.

Troisième étape : PHP est-il même disponible pour l’exécuter ?

  1. Vérifier l’état de PHP-FPM : enfants max atteints, slow logs, backlog.
  2. Consulter les logs d’erreurs du serveur web pour des timeouts upstream et des pics 502/504.
  3. Confirmer qu’OPcache et l’autoload fonctionnent correctement (le cron parcourt souvent des chemins de code rarement utilisés).

Puis : retirer le « trafic » de l’équation

  1. Désactiver les déclencheurs WP-Cron et exécuter le cron WordPress via cron système toutes les minutes ou cinq minutes.
  2. Re-vérifier les événements en retard ; ils devraient se vider de façon prévisible.

Ce playbook est orienté pour trouver rapidement le goulot d’étranglement. Il n’est pas poli. Il fonctionne.

Tâches pratiques : commandes, sorties et décisions (12+)

Voici des commandes exécutables pour un hôte Linux typique exécutant Nginx/Apache + PHP-FPM et WordPress. Ajustez les chemins et utilisateurs, mais conservez l’intention. Chaque tâche inclut : la commande, un exemple de sortie, ce que cela signifie, et la décision à prendre.

Task 1: Confirm the site’s time and timezone at the OS level

cr0x@server:~$ timedatectl
               Local time: Sat 2025-12-27 14:31:19 UTC
           Universal time: Sat 2025-12-27 14:31:19 UTC
                 RTC time: Sat 2025-12-27 14:31:18
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Ce que cela signifie : L’horloge de l’OS est en UTC et synchronisée. Bien. Si ceci affiche System clock synchronized: no ou un fuseau étrange, les publications programmées peuvent dériver ou sembler « manquées ».

Décision : Si NTP n’est pas actif/synchronisé, corriger l’heure d’abord. Déboguer un cron avec une horloge qui dérive, c’est courir après une cible mouvante.

Task 2: Check WordPress timezone and current time via WP-CLI

cr0x@server:~$ cd /var/www/html
cr0x@server:/var/www/html$ wp option get timezone_string
America/New_York
cr0x@server:/var/www/html$ wp eval 'echo "wp_time: ".wp_date("c").PHP_EOL;'
wp_time: 2025-12-27T09:31:33-05:00

Ce que cela signifie : WordPress utilise un fuseau horaire configuré. S’il est vide, WordPress peut se baser sur un offset UTC brut et le comportement DST peut devenir imprévisible.

Décision : Préférer un fuseau nommé (comme America/New_York) plutôt qu’un offset brut. C’est moins surprenant autour des passages DST.

Task 3: Is WP-CLI talking to the right WordPress?

cr0x@server:/var/www/html$ wp core version
6.7.1
cr0x@server:/var/www/html$ wp site list
+----+---------------------+----------------------------+
| id | url                 | last_updated               |
+----+---------------------+----------------------------+
| 1  | https://example.com | 2025-12-27 14:29:05 +0000  |
+----+---------------------+----------------------------+

Ce que cela signifie : Vous opérez sur l’installation prévue et (si multisite) le bon réseau.

Décision : Si ceci génère une erreur, corriger les permissions ou les problèmes de --path avant de continuer. Le débogage à l’aveugle est la façon dont on « corrige » la production/staging.

Task 4: List cron events and find overdue ones

cr0x@server:/var/www/html$ wp cron event list --fields=hook,next_run,recurrence --format=table | head
+-------------------------------+---------------------+------------+
| hook                          | next_run            | recurrence |
+-------------------------------+---------------------+------------+
| wp_version_check              | 2025-12-27 13:55:00 | twice_daily|
| wp_scheduled_delete           | 2025-12-27 14:00:00 | daily      |
| action_scheduler_run_queue    | 2025-12-27 14:01:00 | every_minute|
| wp_update_plugins             | 2025-12-27 12:10:00 | twice_daily|
+-------------------------------+---------------------+------------+

Ce que cela signifie : Si next_run est dans le passé depuis plusieurs minutes/heures et que l’événement ne s’exécute pas, le runner cron est cassé ou bloqué.

Décision : Si des événements sont en retard, passer aux vérifications de loopback et du runner. S’il n’y a aucun événement planifié, investiguer les plugins/thèmes ou la logique de planification.

Task 5: Run cron now, manually, and watch what happens

cr0x@server:/var/www/html$ wp cron event run --due-now
Success: Executed a total of 12 cron events.

Ce que cela signifie : WordPress peut exécuter des événements quand on le lui demande. Cela suggère que le problème provient du déclencheur/runner, pas des handlers d’événements eux-mêmes.

Décision : Si l’exécution manuelle fonctionne, mettre en place cron système et désactiver les déclencheurs WP-Cron. Si l’exécution manuelle échoue avec des fatals, consulter les logs PHP et isoler les plugins.

Task 6: Check whether DISABLE_WP_CRON is set

cr0x@server:/var/www/html$ grep -n "DISABLE_WP_CRON" wp-config.php
91:define('DISABLE_WP_CRON', true);

Ce que cela signifie : WP-Cron est désactivé. C’est acceptable seulement si vous avez un job cron système réel qui l’appelle.

Décision : Si c’est vrai et qu’aucun cron système n’est configuré, vous avez trouvé le bug. Ajouter un job cron système (voir section solutions).

Task 7: Check system cron for a WordPress runner

cr0x@server:~$ sudo crontab -l
no crontab for root

Ce que cela signifie : Root n’a pas de cron. Le runner peut exister sous l’utilisateur web ou dans /etc/cron.d.

Décision : Vérifier l’utilisateur pertinent et les répertoires cron système.

cr0x@server:~$ crontab -l
# m h  dom mon dow   command
*/5 * * * * cd /var/www/html && wp cron event run --due-now --quiet

Ce que cela signifie : Il y a un runner toutes les 5 minutes utilisant WP-CLI. C’est une base raisonnable.

Décision : Si les publications programmées sont toujours manquantes, le runner peut ne pas s’exécuter (permissions, PATH, PHP), ou les jobs échouent.

Task 8: Verify cron actually runs (inspect syslog/journal)

cr0x@server:~$ sudo journalctl -u cron --since "1 hour ago" | tail -n 10
Dec 27 14:25:01 server CRON[21901]: (cr0x) CMD (cd /var/www/html && wp cron event run --due-now --quiet)
Dec 27 14:30:01 server CRON[22188]: (cr0x) CMD (cd /var/www/html && wp cron event run --due-now --quiet)

Ce que cela signifie : Le démon cron exécute le job selon le planning.

Décision : Si vous ne voyez pas d’entrées, cron peut être arrêté, masqué, ou les logs sont ailleurs. Corriger la plateforme d’abord.

Task 9: Confirm the wp-cli command works in cron’s environment

cr0x@server:~$ env -i HOME=/tmp PATH=/usr/bin:/bin bash -lc 'cd /var/www/html && wp cron event run --due-now'
Success: Executed a total of 0 cron events.

Ce que cela signifie : Même avec un environnement minimal (similaire à cron), WP-CLI peut s’exécuter. « 0 events » peut être correct si rien n’est dû.

Décision : Si cela échoue, coder en dur les chemins dans le crontab et s’assurer que WP-CLI est installé là où cron peut le trouver.

Task 10: Test loopback access to wp-cron.php from the server

cr0x@server:~$ curl -I -sS https://example.com/wp-cron.php?doing_wp_cron=1 | head -n 5
HTTP/2 200
content-type: text/html; charset=UTF-8
cache-control: no-cache, must-revalidate, max-age=0
date: Sat, 27 Dec 2025 14:32:10 GMT

Ce que cela signifie : Le point d’accès est joignable et renvoie 200. Bien. Si vous voyez des 301/302 en boucle, 403, 503, ou une page de challenge WAF, les déclencheurs WP-Cron peuvent échouer.

Décision : Si le loopback échoue, corriger DNS/TLS/redirections/règles WAF ou contourner le loopback WP-Cron en utilisant cron système avec WP-CLI.

Task 11: See whether full-page cache is preventing PHP hits

cr0x@server:~$ curl -sS -I https://example.com/ | egrep -i 'x-cache|cf-cache-status|age|server|via'
server: nginx
x-cache: HIT
age: 1842

Ce que cela signifie : Les requêtes peuvent être servies sans toucher PHP. Si votre site a peu de trafic et est majoritairement mis en cache, WP-Cron risque de se déclencher très rarement.

Décision : Ne comptez pas sur les visiteurs pour piloter la planification. Utilisez cron système. Facultativement, excluez /wp-cron.php du cache et du rate limiting.

Task 12: Check PHP-FPM pressure (worker starvation)

cr0x@server:~$ sudo tail -n 20 /var/log/php8.2-fpm.log
[27-Dec-2025 14:20:55] WARNING: [pool www] server reached pm.max_children setting (20), consider raising it
[27-Dec-2025 14:20:56] WARNING: [pool www] child 18734, script '/var/www/html/wp-cron.php' (request: "GET /wp-cron.php?doing_wp_cron=...") executing too slow (12.345 sec), logging

Ce que cela signifie : Le cron tente de s’exécuter, mais PHP est saturé ou lent. Les requêtes WP-Cron peuvent timeout ou ne jamais démarrer.

Décision : Augmenter la capacité (pm.max_children), réduire la charge de requêtes, déplacer le cron vers un pool séparé, ou externaliser les jobs lourds.

Task 13: Look for WordPress fatal errors during cron

cr0x@server:~$ sudo tail -n 30 /var/log/nginx/error.log
2025/12/27 14:21:03 [error] 1229#1229: *9981 FastCGI sent in stderr: "PHP Fatal error:  Uncaught Error: Call to undefined function mb_strlen() in /var/www/html/wp-includes/formatting.php:..."
2025/12/27 14:21:03 [error] 1229#1229: *9981 upstream prematurely closed FastCGI stdout while reading response header from upstream, client: 127.0.0.1, server: example.com, request: "GET /wp-cron.php?doing_wp_cron=..."

Ce que cela signifie : Le cron a échoué sur un fatal PHP (extension manquante dans cet exemple). Cela peut laisser des événements en retard indéfiniment.

Décision : Corriger le runtime PHP sous-jacent. Le cron n’est pas spécial ; il emprunte des chemins de code que les pages normales n’exercent pas toujours.

Task 14: Identify the specific hook that’s piling up (Action Scheduler)

cr0x@server:/var/www/html$ wp action-scheduler list --status=pending --per-page=5
+----------+------------------------------+---------------------+----------+
| hook     | args                         | scheduled_date_gmt   | status   |
+----------+------------------------------+---------------------+----------+
| wc_email | {"id":12345,"type":"follow"} | 2025-12-27 13:58:00 | pending  |
| wc_email | {"id":12346,"type":"follow"} | 2025-12-27 13:59:00 | pending  |
| wc_email | {"id":12347,"type":"follow"} | 2025-12-27 14:00:00 | pending  |
+----------+------------------------------+---------------------+----------+

Ce que cela signifie : L’Action Scheduler de WooCommerce est en retard. Cela peut être « le cron ne s’exécute pas » ou « le cron s’exécute mais ne suit pas la cadence ».

Décision : Exécuter la file manuellement pour tester, puis scaler le runner ou corriger les performances.

Task 15: Run Action Scheduler queue manually (controlled test)

cr0x@server:/var/www/html$ wp action-scheduler run --batch-size=25
Processed 25 actions.

Ce que cela signifie : Les jobs peuvent s’exécuter. Si c’est lent ou s’il y a des erreurs, vous avez une charge/performance ou un bug de plugin, pas seulement un problème de déclencheur.

Décision : Si les exécutions manuelles réussissent, planifier un runner approprié. Si cela échoue, isoler le plugin/hook et corriger la cause racine.

Task 16: Verify the database isn’t the hidden bottleneck (locks, slow queries)

cr0x@server:~$ sudo mysql -e "SHOW PROCESSLIST;" | head
Id	User	Host	db	Command	Time	State	Info
42	wpuser	localhost	wordpress	Query	12	Waiting for table metadata lock	UPDATE wp_options SET option_value='...' WHERE option_name='cron'
77	wpuser	localhost	wordpress	Query	2	Sending data	SELECT * FROM wp_actionscheduler_actions WHERE status='pending' LIMIT 0, 25

Ce que cela signifie : Les mises à jour du cron (stockées dans wp_options) peuvent être bloquées par des verrous de métadonnées. Des changements de schéma lourds, des transactions longues, ou des requêtes lentes peuvent bloquer la planification.

Décision : Si vous voyez des attentes de verrou, trouver le processus bloquant, réduire les transactions longues, et éviter d’exécuter du DDL en période de pointe. Le cron dépend d’écritures d’options rapides.

Solutions efficaces : du pansement rapide à la planification réelle

Fix 1: Stop relying on traffic — use system cron + WP-CLI

C’est la solution adulte. Si vous êtes sur un VPS, bare metal, ou tout hôte où vous contrôlez cron, faites-le.

  1. Désactiver le déclencheur WP-Cron en ajoutant ceci à wp-config.php :
    cr0x@server:~$ sed -n '1,120p' /var/www/html/wp-config.php | tail -n 5
    define('DB_COLLATE', '');
    define('DISABLE_WP_CRON', true);
    /* That's all, stop editing! Happy publishing. */
    
  2. Ajouter une entrée crontab (toutes les 1–5 minutes selon la charge) :
    cr0x@server:~$ crontab -e
    */1 * * * * cd /var/www/html && /usr/local/bin/wp cron event run --due-now --quiet
    

Pourquoi WP-CLI plutôt que frapper wp-cron.php ? Moins de pièces mobiles. Pas de WAF. Pas de TLS. Pas de redirections. Pas de cache. Juste PHP qui exécute WordPress dans un contexte contrôlé.

Compromis : Vous devez maintenir WP-CLI installé et accessible. Ce n’est pas difficile. C’est un fichier.

Fix 2: If you can’t run WP-CLI, run wp-cron.php from cron anyway

Parfois vous êtes sur un hébergement mutualisé verrouillé ou une plateforme où WP-CLI est absent. Vous pouvez toujours utiliser cron système pour appeler l’endpoint, mais vous revenez à la fragilité HTTP.

cr0x@server:~$ crontab -e
*/2 * * * * curl -sS -o /dev/null https://example.com/wp-cron.php?doing_wp_cron=1

Décision : Utiliser cela seulement si vous n’avez pas le choix. Si cela échoue de façon intermittente à cause d’un WAF/ratelimit, le problème réapparaîtra silencieusement.

Fix 3: Fix loopback failures (403/401/redirects/WAF)

Les échecs de loopback sont les plus agaçants car le site fonctionne bien pour les humains. C’est seulement l’appel interne qui échoue.

  • Authentification basique sur staging ou production : Le loopback reçoit un 401. Corrigez en autorisant les IP de loopback ou configurez cron pour utiliser WP-CLI.
  • Redirections HTTPS forcées / boucles : Mauvais siteurl/home ou en-têtes proxy mal configurés. Corriger la configuration du reverse proxy et les options d’URL WordPress.
  • WAF/protection bot : Les challenges bloquent les requêtes non-navigateur. Exempter /wp-cron.php ou arrêter d’utiliser le loopback HTTP entièrement.

Fix 4: Exclude wp-cron.php from caching and aggressive rate limiting

Même si vous exécutez cron via cron système, vous pouvez avoir des plugins qui déclenchent encore des loopbacks. Rendez les endpoints cron « ennuyeux ».

Au minimum, ne mettez pas en cache /wp-cron.php et évitez d’appliquer des challenges bot dessus. Si votre CDN insiste pour être « utile », il finira par vous aider à manquer une planification.

Fix 5: Separate PHP capacity for cron (serious sites do this)

Si votre front-end WordPress est très sollicité, le cron concurrence les mêmes workers. Sous charge, le cron devient une victime d’épuisement. Ou pire : le cron démarre et ralentit le front-end. Choisissez votre poison.

Options :

  • Pool PHP-FPM dédié pour les chemins d’administration et cron, avec ses propres limites.
  • Hôte/conteneur runner séparé qui exécute WP-CLI contre la même base de code et base de données.
  • Externaliser les tâches lourdes hors de WordPress (envoi d’e-mails, exports, flux) et laisser WordPress uniquement les enqueuer.

Fix 6: Deal with “cron lock” and stuck transients

WordPress utilise un mécanisme de verrouillage (stocké dans options/transients) pour éviter les exécutions concurrentes du cron. Si un processus meurt en cours d’exécution, vous pouvez obtenir le pattern « cron verrouillé » où rien ne s’exécute jusqu’à l’expiration d’une fenêtre.

Privilégiez le diagnostic de pourquoi le cron meurt (fatals, mémoire, timeouts), mais quand il faut débloquer la production :

cr0x@server:/var/www/html$ wp option list --search=cron --fields=option_name,autoload --format=table | head
+----------------+----------+
| option_name    | autoload |
+----------------+----------+
| cron           | yes      |
+----------------+----------+

Décision : Ne pas supprimer l’option cron ; c’est le magasin de planning. Si vous suspectez un verrou obsolète, traiter le transient doing_cron s’il est présent, mais seulement après avoir confirmé qu’aucune exécution n’est active.

Fix 7: Make scheduled publishing resilient (don’t bet the business on one mechanism)

Si la publication à l’heure est critique pour le chiffre d’affaires (publicités, campagnes, avis légaux), envisagez une stratégie « ceinture et bretelles » :

  • Le cron système s’exécute chaque minute.
  • Un monitor externe vérifie les posts en retard et alerte l’équipe SRE/ops si cela se reproduit.
  • Les éditeurs reçoivent des avertissements UI pour les horaires manqués (ce n’est pas une solution, mais un signal plus rapide).

Citation (idée paraphrasée) : Werner Vogels a souvent répété que « tout échoue, tout le temps », donc les systèmes doivent être conçus pour s’attendre à l’échec et le tolérer.

Blague #2 : Si votre plan d’intervention est « rafraîchir la page d’accueil jusqu’à ce qu’elle publie », félicitations — vous avez inventé le cron piloté par l’humain.

Erreurs courantes : symptôme → cause racine → solution

Voici la partie où on arrête d’être gentil avec nos anciens choix.

1) Les publications programmées ne se publient jamais sur les sites à faible trafic

Symptôme : Les articles programmés la nuit ne sont publiés que lorsqu’un visiteur arrive sur le site.

Cause racine : WP-Cron dépend des chargements de page pour se déclencher.

Solution : Désactiver les déclencheurs WP-Cron et exécuter cron système avec WP-CLI toutes les 1–5 minutes.

2) Pics de « Missed schedule » après activation d’un nouveau cache/CDN

Symptôme : Tout fonctionnait ; puis le cache a été activé et les événements programmés se sont arrêtés.

Cause racine : Le cache sert les pages sans PHP, supprimant ainsi le « tic » WP-Cron. Parfois /wp-cron.php lui-même est mis en cache ou limité en taux.

Solution : Utiliser cron système. Exempter /wp-cron.php du cache et des challenges WAF.

3) wp-cron.php retourne 403 seulement depuis le serveur

Symptôme : Les navigateurs obtiennent 200 ; le loopback depuis le serveur reçoit 403 ou une page de challenge.

Cause racine : Les règles WAF traitent les requêtes originant du serveur comme des bots ; ou l’IP egress est différente ; ou les en-têtes ne correspondent pas aux attentes.

Solution : Mettre en liste blanche l’IP egress du serveur pour /wp-cron.php, ou arrêter d’utiliser le loopback et exécuter WP-CLI depuis cron.

4) Le cron s’exécute, mais l’arriéré ne se résorbe jamais

Symptôme : Les événements cron sont en retard, l’exécution manuelle en traite quelques-uns, mais la pile reste.

Cause racine : La charge est trop lourde pour la cadence/capacité ; Action Scheduler nécessite plus de débit ; les timeouts PHP tuent les exécutions en cours de lot.

Solution : Augmenter la fréquence du runner, ajuster prudemment la taille des lots, ajouter de la capacité PHP, et enquêter sur les hooks lents. Envisager un pool worker séparé.

5) DISABLE_WP_CRON défini sans vrai runner

Symptôme : Tout ce qui est planifié cesse après un changement de tuning.

Cause racine : Quelqu’un a désactivé WP-Cron pour « réduire les requêtes » et a oublié le remplacement cron système.

Solution : Ajouter un cron système avec WP-CLI ; vérifier via les logs qu’il s’exécute.

6) Loopback casse à cause de mauvaises URLs après une migration

Symptôme : L’endpoint cron redirige en boucle ou pointe vers le mauvais domaine.

Cause racine : home/siteurl désaccordés, ou en-têtes proxy mal configurés, poussant WordPress à générer de mauvaises URLs internes.

Solution : Corriger les options d’URL, ajuster les en-têtes proxy, et retester le curl vers wp-cron.php.

7) Le cron échoue seulement en période de pointe

Symptôme : La nuit tout va bien ; les lancements et ventes provoquent des horaires manqués.

Cause racine : Saturation PHP-FPM ; les requêtes cron sont mises en file d’attente ou tuées.

Solution : Séparer le cron dans son propre pool/hôte, augmenter la capacité FPM, optimiser les plugins coûteux, et limiter la charge cron.

8) Échecs « aléatoires » liés au DNS

Symptôme : Le loopback wp-cron.php échoue de façon intermittente avec des timeouts.

Cause racine : Le serveur résout le domaine public vers une IP externe, effectue un hairpin à travers un firewall/CDN, ou rencontre des problèmes IPv6 ; parfois le DNS interne diffère du public.

Solution : Utiliser un runner WP-CLI. Si vous devez effectuer un loopback, corriger la résolution DNS/interne et préférer des schémas d’invocation directs sur localhost.

Trois mini-histoires d’entreprise du pays du « ça aurait dû marcher »

Mini-histoire #1 : L’hypothèse erronée (« Cron s’exécute parce qu’il y a du trafic »)

L’équipe marketing d’une entreprise SaaS de taille moyenne a placé son blog WordPress derrière un nouveau CDN brillant. La vitesse de page s’est améliorée. Les scores Lighthouse ont été exhibés. Tout le monde a gagné.

Deux semaines plus tard, un article programmé annonçant un webinaire ne s’est pas publié. Quelqu’un l’a remarqué une heure après l’heure de début. La théorie immédiate fut une erreur éditoriale : « Ils ont oublié de cliquer sur Publier ». La seconde théorie évoquait les fuseaux : « Peut-être qu’il est en UTC ? » Roulette classique des blâmes.

Sur le serveur, la file cron WordPress montrait des événements en retard. L’exécution manuelle de wp cron event run --due-now publia immédiatement l’article. C’était l’indice : le système pouvait exécuter ; il n’était juste pas déclenché.

L’hypothèse erronée était subtile : « Nous avons du trafic, donc WP-Cron va s’exécuter. » Mais le CDN servait la plupart des pages depuis le cache. PHP n’était pas sollicité. Les visiteurs étaient réels ; leurs requêtes ne touchaient jamais WordPress. WP-Cron avait été efficacement débranché.

La solution fut simple et un peu embarrassante : désactiver les déclencheurs WP-Cron, installer WP-CLI, et exécuter cron système chaque minute. Après cela, la publication est redevenue une fonction du temps, pas des vues de page.

Mini-histoire #2 : L’optimisation qui se retourne contre vous (« Désactiver wp-cron.php pour la performance »)

Une équipe ops a hérité d’un cluster WordPress avec des pics de latence occasionnels. Quelqu’un a trouvé des articles affirmant que wp-cron.php est « lent » et a décidé d’optimiser. Ils ont bloqué l’accès à /wp-cron.php à la périphérie et mis DISABLE_WP_CRON dans wp-config.php.

Pris isolément, ce n’est pas insensé. Mais ils n’ont pas ajouté de remplacement cron système. Le site semblait correct car le front-end servait encore du contenu. La première panne visible survint quelques jours plus tard : les abonnements WooCommerce ne se sont pas renouvelés à temps, et une pile d’actions « pending » s’est accumulée.

Quand ils ont finalement enquêté, ils ont trouvé une backlog suffisamment grande pour transformer « réactiver cron » en mini-DDoS auto-infligé. La première exécution réactivée a tenté d’exécuter des heures de tâches en retard sous charge maximale. Les workers PHP-FPM ont atteint leurs limites. Les clients ont reçu des 502. La correction a provoqué un second incident.

La résolution n’a pas été juste « remettre cron ». Ils ont introduit un runner contrôlé : cron WP-CLI chaque minute, cadence Action Scheduler ajustée, et plafonnement des tailles de lot. Ils ont aussi appris à traiter les changements « disable » comme des modifications de production avec plans de rollback.

L’optimisation, c’est bien. L’optimisation sans plan de remplacement, c’est juste un incident plus lent.

Mini-histoire #3 : La pratique ennuyeuse qui a sauvé la mise (runner séparé + monitoring)

Un éditeur multi-sites WordPress avait déjà été brûlé. Ils ne faisaient pas confiance à WP-Cron, alors ils ont construit une configuration terne et efficace : chaque site avait DISABLE_WP_CRON activé et un job cron système exécutant WP-CLI. Ils enregistraient les temps d’exécution et comptaient les événements en retard comme une métrique.

Un matin, un problème de stockage sur une VM a provoqué des pics de latence I/O. Le front-end est resté majoritairement disponible grâce au cache, mais les processus PHP ont ralenti. Le job cron s’exécutait toujours, mais il a commencé à prendre plus de temps que prévu, et la métrique « événements en retard » a commencé à monter.

Personne n’a attendu qu’un éditeur se plaigne. L’alerte s’est déclenchée lorsque les événements en retard ont dépassé un seuil pendant plusieurs minutes. L’ingénieur on-call a vérifié les logs, vu des warnings PHP-FPM lents, et corrélé cela avec la latence disque. Ils ont basculé temporairement l’exécution cron vers un runner moins impacté et ont limité les tâches d’arrière-plan jusqu’à stabilisation du stockage.

Les publications programmées se sont publiées à l’heure ? Pour la plupart, oui. Certaines ont été retardées de quelques minutes, mais rien n’a manqué d’une heure. Le métier n’a presque rien remarqué. C’est l’objectif : des pannes silencieuses car vos contrôles ennuyeux les ont attrapées tôt.

Ils n’ont pas réussi parce qu’ils étaient brillants. Ils ont réussi parce qu’ils étaient préparés.

Faits et contexte intéressants (pourquoi ce design existe)

WP-Cron paraît étrange si vous venez des systèmes traditionnels. Il l’est moins quand vous vous souvenez des environnements que WordPress ciblait à l’origine.

  1. WP-Cron existe principalement parce que l’hébergement mutualisé n’offrait pas toujours un accès cron. WordPress devait planifier des tâches sans supposer de privilèges au niveau OS.
  2. C’est un « pseudo-cron » par conception. Il s’appuie sur les requêtes de page pour approximativement réaliser une planification temporelle.
  3. WordPress stocke les plannings cron dans la base de données (dans wp_options). Cela le rend portable, mais le rend aussi sensible aux verrous DB et aux écritures lentes.
  4. La requête loopback est à la fois une fonctionnalité et une responsabilité. Elle évite d’exécuter du travail lourd pendant la requête utilisateur, mais dépend du bon fonctionnement HTTP en interne.
  5. Certains plugins contournent la loopback et exécutent des tâches lors des chargements normaux. Cela peut « fonctionner » jusqu’à provoquer des pics de latence sous charge. Fiabilité vs performance, le compromis éternel.
  6. Action Scheduler de WooCommerce est devenu courant car WP-Cron seul était trop approximatif pour les charges lourdes. Il ajoute de la persistance, des retries et de la visibilité — mais a toujours besoin d’un runner.
  7. Le symptôme « missed schedule » n’est souvent pas la logique de planification de l’article. C’est le runner qui n’a pas exécuté à l’instant de publication, laissant les posts en suspens jusqu’au prochain tic cron.
  8. Le caching moderne rend WP-Cron moins fiable qu’en 2008. Les caches réduisent les hits PHP, et WP-Cron dépend des hits PHP pour se déclencher.
  9. Les reverse proxies et l’application forcée de HTTPS ont introduit de nouveaux modes d’échec de loopback. Des en-têtes incorrects ou des réglages d’URL mixtes peuvent provoquer des redirections internes que seul le cron voit.

Listes de contrôle / plan étape par étape

Checklist A: Immediate triage (15 minutes)

  1. Confirmer la synchro de l’heure OS (timedatectl). Si non synchronisée, corriger NTP d’abord.
  2. Lister les événements cron dus (wp cron event list). Si en retard, le runner est cassé.
  3. Exécuter manuellement les événements dus (wp cron event run --due-now) pour restaurer le service.
  4. Vérifier si WP-Cron est désactivé (grep DISABLE_WP_CRON).
  5. Faire un curl sur /wp-cron.php et capturer le code HTTP. 200 est bon ; tout autre résultat est un indice.
  6. Consulter les logs PHP-FPM et web pour timeouts/fatals autour des tentatives cron.

Checklist B: Permanent fix for most sites (60–90 minutes)

  1. Installer WP-CLI dans un emplacement stable (package manager ou phar vérifié), s’assurer qu’il s’exécute sous le bon utilisateur.
  2. Mettre define('DISABLE_WP_CRON', true); dans wp-config.php.
  3. Créer un job cron système exécutant WP-CLI chaque minute ou toutes les cinq minutes.
  4. Vérifier l’exécution via journalctl -u cron et confirmer que les événements en retard se vident.
  5. Si WooCommerce/Action Scheduler est présent, valider la santé de la file (wp action-scheduler list).
  6. Exclure quand même /wp-cron.php du cache/WAF. D’autres plugins peuvent encore l’appeler.

Checklist C: Hardening for high-load or revenue-critical sites

  1. Séparer la capacité d’exécution cron (pool PHP-FPM dédié ou hôte runner).
  2. Mesurer les événements en retard et la profondeur des files ; alerter quand les seuils sont dépassés.
  3. Limiter le runtime : ajuster les tailles de lots, éviter les patterns « exécuter tout » qui provoquent des thundering herds.
  4. Auditer les tâches planifiées : supprimer les jobs redondants, réduire les fréquences, et éviter le travail lourd dans les requêtes pages.
  5. Prévoir l’échec : un plan de replay d’arriéré qui ne va pas écraser votre front-end.

FAQ

1) Pourquoi les publications programmées échouent quand le site n’a pas de visiteurs ?

Parce que WP-Cron est déclenché par des requêtes sur le site. Pas de requêtes, pas de déclencheur. Utilisez cron système (ou un job runner) pour invoquer WordPress selon un planning.

2) Dois-je désactiver WP-Cron ?

Oui — si vous le remplacez par un cron système. Le désactiver sans remplacement est une manière sûre de casser les publications programmées et les tâches en arrière-plan.

3) Appeler wp-cron.php depuis cron système suffit-il ?

Ça peut fonctionner, mais c’est fragile (HTTP, WAF, TLS, redirections). WP-CLI est plus fiable car il évite tout le chemin web.

4) Mon wp-cron.php renvoie 200. Pourquoi des tâches sont encore en retard ?

200 signifie seulement que l’endpoint répond. Le cron peut échouer à cause de fatals PHP, timeouts, verrous DB, ou épuisement des workers. Vérifiez les logs et exécutez wp cron event run --due-now pour voir si l’exécution fonctionne.

5) Le cache casse-t-il WP-Cron ?

Souvent, indirectement, oui. Le cache de page complète réduit les hits PHP, ce qui réduit les déclencheurs WP-Cron. Il peut aussi interférer directement si /wp-cron.php est mis en cache ou limité en taux.

6) Et les « Scheduled Actions » de WooCommerce ?

C’est Action Scheduler. Il est plus visible que le WP-Cron natif mais a toujours besoin d’un runner. Utilisez WP-CLI pour l’exécuter et assurez-vous que votre cadence cron et votre capacité peuvent vider la file.

7) Les fuseaux horaires peuvent-ils causer des « missed schedule » ?

Oui, surtout autour du DST et avec des réglages de fuseau mal configurés. Confirmez la synchro OS, puis le fuseau WordPress et l’heure courante via WP-CLI.

8) À quelle fréquence le cron système doit-il tourner ?

Choix communs : chaque minute pour les sites occupés avec beaucoup de travail en file, ou toutes les cinq minutes pour les sites légers. La bonne fréquence maintient les « en retard » proches de zéro.

9) WP-Cron présente-t-il un risque de sécurité ?

Pas intrinsèquement, mais exposer wp-cron.php sur Internet peut attirer du trafic bruyant. Si vous utilisez WP-CLI avec cron système, vous pouvez réduire la dépendance externe et simplifier la surface exposée.

10) Et si je suis sur un hébergement géré et que je ne peux pas utiliser cron système ?

Utilisez la facility de tâches planifiées que l’hébergeur fournit. Beaucoup proposent des « cron jobs » dans un panneau ou une API de scheduler. Si tout ce que vous pouvez faire est HTTP, appelez /wp-cron.php et exemptez-le des règles WAF/caching.

Conclusion : prochaines étapes pour éviter les récidives

Les publications programmées WordPress échouent pour la même raison que la plupart des systèmes de production : des hypothèses cachées. L’hypothèse ici est que le travail temporel s’exécutera parce que du trafic web existe et que l’HTTP interne se comportera. Ce n’est pas de l’ingénierie ; c’est de l’espoir avec un CMS.

Faites la chose pratique :

  1. Vérifiez la file et exécutez manuellement les événements dus pour restaurer le service.
  2. Cessez de compter sur les déclencheurs WP-Cron. Utilisez cron système avec WP-CLI.
  3. Renforcez le chemin : excluez /wp-cron.php du cache/WAF, et séparez la capacité cron si vous avez de la charge.
  4. Ajoutez un monitor : événements en retard ou backlog Action Scheduler. Attrapez la prochaine panne avant que vos éditeurs ne la voient.

Rendez la planification ennuyeuse. Votre calendrier éditorial mérite mieux que « quelqu’un a visité la page d’accueil au bon moment ».

← Précédent
Ubuntu 24.04 : performances SSD/NVMe qui se dégradent avec le temps : prouvez que c’est le TRIM/GC et réparez-le
Suivant →
Google Glass : quand le futur se sentait maladroit en public

Laisser un commentaire