Désactiver WP-Cron et utiliser system cron
WP-Cron dépend des visites et peut ralentir ou accumuler les tâches. Désactivez-le, lancez-le avec WP-CLI via system cron et vérifiez son exécution.
Ce qu’est wp-cron et pourquoi system cron le remplace
WP-Cron est le planificateur de tâches intégré à WordPress. Il ne s’exécute que lorsqu’une personne demande une page. Rien dans WordPress ne le réveille automatiquement. Pour chaque requête qui n’est pas servie depuis un cache, WordPress lit la liste des tâches planifiées. Si une tâche doit être exécutée, il envoie une seconde requête HTTP vers /wp-cron.php pour effectuer le travail. Déplacer cette tâche vers system cron vous garantit une exécution prévisible selon un calendrier fixe, que le site ait compté mille visiteurs pendant cette minute ou aucun.
Deux lignes effectuent le travail : une constante dans wp-config.php et une entrée crontab. Tout le reste de ce guide concerne les éléments que ces deux lignes ne précisent pas : avec quel utilisateur la tâche doit s’exécuter, comment vérifier que les événements planifiés ont bien été exécutés et les trois façons dont cette configuration peut échouer sans rien afficher sur le site.
Les exemples utilisent /srv/www/example.com comme répertoire WordPress et www-data comme utilisateur du serveur web. Remplacez ces chemins et cet utilisateur par les vôtres partout.
Le coût de WP-Cron déclenché par les visiteurs sur un site très fréquenté
Chaque requête sans cache paie le coût de cette vérification. WordPress charge l’option cron, compare les horodatages et, lorsqu’une tâche doit être exécutée, appelle spawn_cron(). Cette fonction envoie une requête loopback non bloquante vers /wp-cron.php. Le visiteur n’attend pas le résultat. En revanche, un worker PHP l’exécute. Sur un petit VPS utilisant PHP-FPM avec pm.max_children = 5, une tâche planifiée lente peut monopoliser un cinquième de la capacité PHP pendant toute sa durée. Elle a aussi le plus de chances d’être déclenchée pendant votre minute la plus chargée, car c’est à ce moment que le nombre de chargements de page est le plus élevé.
WordPress limite les doublons. Il utilise un verrou dont la durée de vie est WP_CRON_LOCK_TIMEOUT, soit 60 secondes par défaut. Ainsi, plusieurs visiteurs simultanés ne lancent pas chacun une exécution. Le verrou limite les doublons. Il ne déplace pas le traitement en dehors du chemin de la requête.
Mesurez la fréquence d’exécution sur votre propre serveur avant de déterminer si elle est importante. Chaque requête loopback apparaît dans le journal d’accès du serveur web :
sudo grep -c 'wp-cron.php' /var/log/nginx/access.logApache écrit plutôt dans /var/log/apache2/access.log. Plusieurs milliers d’exécutions par jour représentent un coût réel. C’est le type de valeur que vous devez mesurer sur votre propre serveur plutôt que de la reprendre d’un article, comme lorsque vous évaluez les performances d’un VPS avant et après toute autre modification.
La mise en cache change la situation. Si un page cache sert la plupart des requêtes sous forme de HTML statique, PHP ne s’exécute pas pour ces requêtes et la vérification cron n’a donc pas lieu. Un site très fréquenté et fortement mis en cache commence à se comporter comme le site peu fréquenté présenté ci-dessous.
Quel visiteur a déclenché les tâches cron sur un site peu fréquenté
Sans visiteur, pas de cron. Un site qui reçoit quelques visites par jour exécute ses tâches planifiées quelques fois par jour, à des moments aléatoires, lorsque ces visites arrivent.
Les symptômes sont toujours les mêmes. Un article programmé pour 09:00 reste dans la liste des articles avec l’état Missed schedule jusqu’à ce qu’une personne charge une page. Les extensions de sauvegarde ignorent la nuit. Les vérifications de mises à jour prennent du retard : le tableau de bord n’affiche aucune mise à jour alors qu’une mise à jour de sécurité est déjà disponible. Les e-mails de commande, les notifications de renouvellement et les avertissements d’expiration partent en retard.
Rien de tout cela ne génère d’erreur dans les journaux. Du point de vue de WordPress, la tâche n’était jamais en retard, car elle n’avait jamais été démarrée.
Étape 1 : désactiver le déclencheur de visiteurs dans wp-config.php
Ouvrez /srv/www/example.com/wp-config.php et ajoutez la constante suivante :
define( 'DISABLE_WP_CRON', true );Placez-la au-dessus de la ligne /* That's all, stop editing! Happy publishing. */, car la ligne située juste sous ce commentaire nécessite wp-settings.php, et wp-settings.php est l’endroit où WordPress rattache la vérification du cron à init. Une constante définie après ce require est prise en compte trop tard pour modifier le comportement. Le fichier semble alors correct, mais le déclencheur continue de fonctionner.
Vérifiez que la ligne se trouve bien à l’emplacement prévu :
grep -n "DISABLE_WP_CRON\|stop editing" /srv/www/example.com/wp-config.phpLa constante n’empêche pas la planification des événements. Les plugins continuent d’ajouter des tâches à la file d’attente, exactement comme avant. Elle empêche uniquement les chargements de pages d’exécuter cette file. La file n’est donc plus exécutée jusqu’à la fin de l’étape 3.
Elle ne bloque pas non plus les requêtes directes vers /wp-cron.php. N’importe qui peut toujours demander cette URL. Cela est généralement sans conséquence, car le fichier exécute uniquement les tâches arrivées à échéance. Le blocage dans la configuration du serveur web est facultatif. Si vous le bloquez, le fallback curl situé vers la fin de ce guide ne fonctionnera plus non plus.
Étape 2 : installer WP-CLI
WP-CLI est l’outil officiel en ligne de commande pour WordPress. Il nécessite le binaire PHP en ligne de commande, qui est fourni par un paquet distinct du module PHP du serveur web.
php -v
sudo apt install -y php-cliInstallez WP-CLI à partir du build phar, comme le recommande le guide d’installation officiel :
cd /tmp
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
php wp-cli.phar --info
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
wp --infophp wp-cli.phar --info affiche le chemin du binaire PHP, la version de PHP et la version de WP-CLI. Si les trois éléments s’affichent, le phar fonctionne. En août 2026, le guide d’installation indique PHP 7.2.24 comme version minimale, et Ubuntu 24.04 fournit PHP 8.3 : un serveur récent dépasse donc largement cette version minimale. Effectuez ensuite les mises à jour avec sudo wp cli update.
Exécutez WP-CLI avec l’utilisateur du site, jamais avec root :
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com core versionAvec root, WP-CLI refuse de démarrer :
Error: YIKES! It looks like you're running this as root.Il propose --allow-root. Ne l’utilisez pas ici. La raison est expliquée dans le premier cas d’échec ci-dessous.
Notez également que sudo -u www-data -i ne fonctionne pas, car le shell de connexion de ce compte est /usr/sbin/nologin et vous obtenez This account is currently not available.. Passer directement la commande à sudo -u contourne le shell de connexion et l’exécution fonctionne donc correctement.
Vérifiez maintenant que WordPress voit bien la constante de l’étape 1 :
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com eval 'var_dump( DISABLE_WP_CRON );'Cela affiche bool(true). Une erreur fatale indiquant qu’une constante n’est pas définie signifie que la ligne define() n’est pas atteinte. En général, elle a été placée après la directive require.
Étape 3 : ajoutez l’entrée cron avec le bon utilisateur
Le bon utilisateur est celui qui possède les fichiers écrits par PHP. Vérifiez les deux éléments :
stat -c '%U %G' /srv/www/example.com/wp-content/uploads
grep -E '^(user|group) = ' /etc/php/8.3/fpm/pool.d/www.confSur une installation Ubuntu par défaut, les deux commandes renvoient www-data. Si vous avez attribué au site un pool PHP-FPM dédié avec son propre utilisateur, ce qui est généralement le cas dans une configuration par site basée sur une pile LAMP sur Ubuntu 24.04, utilisez cet utilisateur pour tout ce qui suit.
Créez un répertoire de journaux accessible en écriture à cet utilisateur :
sudo install -d -o www-data -g www-data -m 750 /var/log/wp-cronModifiez la crontab de cet utilisateur :
sudo crontab -u www-data -eAjoutez une ligne :
*/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-example.lock /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now >> /var/log/wp-cron/example.log 2>&1Examinons cette ligne élément par élément. */5 l’exécute toutes les cinq minutes. flock -n prend un fichier de verrouillage et s’arrête immédiatement si une exécution précédente le détient encore. /usr/local/bin/wp correspond au chemin absolu, requis par cron. --path permet d’exécuter la commande depuis n’importe quel répertoire de travail. --due-now traite uniquement les événements dont l’heure est arrivée, au lieu de traiter tous les événements de la file d’attente. La redirection envoie la sortie standard et les erreurs dans un seul fichier que vous pouvez consulter.
En pratique, cette redirection est indispensable. Cron envoie la sortie d’une tâche par e-mail à son utilisateur, mais la plupart des images VPS n’ont aucun mail transfer agent installé. Cron journalise alors (CRON) info (No MTA installed, discarding output) et supprime la sortie. Un fichier conserve les éléments nécessaires au diagnostic.
Vérifiez que le fichier a bien été enregistré :
sudo crontab -u www-data -lPour plusieurs sites, utilisez une ligne par site et décalez les minutes afin qu’ils ne démarrent pas tous en même temps :
*/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-one.lock /usr/local/bin/wp --path=/srv/www/one.example.com cron event run --due-now >> /var/log/wp-cron/one.log 2>&1
2-59/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-two.lock /usr/local/bin/wp --path=/srv/www/two.example.com cron event run --due-now >> /var/log/wp-cron/two.log 2>&1Le journal grossit indéfiniment si vous ne le faites pas tourner. Écrivez /etc/logrotate.d/wp-cron :
/var/log/wp-cron/*.log {
weekly
rotate 4
missingok
notifempty
compress
create 640 www-data www-data
}Vérifiez que la configuration est analysée sans rien modifier : sudo logrotate --debug /etc/logrotate.d/wp-cron.
Étape 4 : confirmer que les événements planifiés se sont réellement exécutés
Le fait qu’une ligne de crontab ait été enregistrée sans erreur ne prouve rien. Commencez par le contrôle le moins coûteux, puis passez à celui qui permet de conclure.
Tout d’abord, cron a-t-il lancé la commande ? Cron écrit dans le journal sous sa propre unité :
journalctl -u cron.service --since "15 min ago" | grep wpUne entrée correcte ressemble à ceci, après suppression de l’horodatage et du nom d’hôte au début :
CRON[24913]: (www-data) CMD (/usr/bin/flock -n /run/lock/wp-cron-example.lock /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now >> /var/log/wp-cron/example.log 2>&1)Cette ligne signifie que cron a lancé votre commande avec l’utilisateur www-data. Elle ne permet pas de savoir si la commande a fonctionné.
Ensuite, WordPress a-t-il exécuté quelque chose ? Lisez le fichier journal :
sudo tail -n 20 /var/log/wp-cron/example.logWP-CLI affiche une ligne par événement, puis un total :
Executed the cron event 'wp_version_check' in 0.418s.
Success: Executed a total of 2 cron events.Les erreurs sont écrites dans le même fichier, ce qui est précisément l’objectif de 2>&1. La plupart des exécutions n’auront aucun événement à traiter et écriront très peu de contenu. Lisez donc le fichier après une exécution pour laquelle vous savez que des tâches étaient en attente.
Enfin, vérifiez le fonctionnement de bout en bout. Planifiez un événement marqueur et observez sa disparition :
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event schedule wp_cli_cron_check now
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event list --fields=hook,next_run_relative --format=csv | grep wp_cli_cron_checkAttendez un intervalle, puis exécutez de nouveau la commande de liste. Le hook a disparu, car un événement ponctuel est retiré de la file lorsqu’il s’exécute. Aucun plugin n’enregistre de callback pour ce nom de hook ; son exécution ne fait donc rien d’autre sur le site. Si le hook est toujours listé après deux intervalles, la file n’est pas traitée. Les deux premiers contrôles vous indiquent alors si le problème concerne cron ou WP-CLI.
N’utilisez pas wp cron test pour ce contrôle. Cette commande vérifie si le lancement déclenché par un visiteur fonctionne, et elle renvoie une erreur lorsque DISABLE_WP_CRON est vrai. Sur un serveur correctement configuré, cette erreur est le résultat attendu, et non un dysfonctionnement.
L’alternative avec un timer systemd
Si les tâches planifiées du reste du serveur utilisent déjà des services et timers systemd, ajoutez-y aussi WordPress. Chaque exécution apparaît alors dans systemctl list-timers, et la sortie est envoyée au journal au lieu d’être écrite dans un fichier qu’il faut faire tourner.
Créez /etc/systemd/system/wp-cron-example.service :
[Unit]
Description=Run due WordPress cron events for example.com
[Service]
Type=oneshot
User=www-data
Group=www-data
ExecStart=/usr/local/bin/wp --path=/srv/www/example.com cron event run --due-nowPuis /etc/systemd/system/wp-cron-example.timer :
[Unit]
Description=Run WordPress cron for example.com every 5 minutes
[Timer]
OnCalendar=*:0/5
AccuracySec=30s
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl enable --now wp-cron-example.timer
systemctl list-timers wp-cron-example.timer
journalctl -u wp-cron-example.service -n 20systemd n’exécute pas deux instances du même service simultanément. Cette version n’a donc pas besoin de flock. Persistent=true lui permet de rattraper une exécution manquée pendant que la machine était arrêtée, ce qu’une entrée crontab ne peut pas faire.
Choisissez la crontab ou le timer. Les exécuter tous les deux pour le même site vide la queue deux fois. Les exécutions en double d’une tâche d’e-mail ou de commande sont visibles par vos clients.
Le job cron ne doit pas s’exécuter en tant que root
C’est le premier des trois cas qui font échouer la configuration. Si vous placez le job dans la crontab de root, WP-CLI s’arrête avant toute autre opération :
Error: YIKES! It looks like you're running this as root.La file d’attente ne s’exécute jamais. Si vous n’avez pas redirigé la sortie, vous ne voyez pas le message. La correction dangereuse consiste à ajouter --allow-root, car tous les fichiers écrits par un plugin pendant cette exécution appartiennent alors à root. La requête web suivante s’exécute en tant que www-data, ne peut pas écrire dans ces répertoires et le site commence à afficher des messages comme :
Unable to create directory wp-content/uploads/2026/08. Is its parent directory writable by the server?Rétablissez le propriétaire, puis déplacez le job :
sudo chown -R www-data:www-data /srv/www/example.com/wp-contentLa crontab de root et celle de www-data sont des fichiers distincts. Supprimer la ligne dans l’une ne modifie pas l’autre. Vérifiez les deux :
sudo crontab -u root -l
sudo crontab -u www-data -lPourquoi cron signale wp: not found
Il s’agit du deuxième échec. Pour les tâches utilisateur, cron fournit un PATH très court, /usr/bin:/bin. WP-CLI s’installe dans /usr/local/bin, qui n’en fait pas partie. La tâche démarre, échoue en une fraction de seconde, et le journal contient une seule ligne :
/bin/sh: 1: wp: not foundVérifiez directement l’environnement de cron au lieu de faire des suppositions. Ajoutez temporairement cette ligne :
*/5 * * * * env > /tmp/cron-env.txt 2>&1Consultez /tmp/cron-env.txt après un intervalle, puis supprimez la ligne. La valeur PATH= dans ce fichier correspond exactement à l’environnement de votre tâche.
Deux solutions sont possibles. Utilisez le chemin absolu /usr/local/bin/wp, comme à l’étape 3. Vous pouvez aussi définir le PATH une seule fois en haut de la crontab, avant toutes les lignes de tâches :
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binLe même problème se produit un niveau plus bas. Le fichier phar wp commence par #!/usr/bin/env php ; le shell doit donc aussi pouvoir trouver php. Si PHP se trouve en dehors de /usr/bin, ce qui arrive avec les builds personnalisés et ceux des panneaux de contrôle, vous obtenez :
/usr/bin/env: 'php': No such file or directoryDans ce cas, appelez explicitement l’interpréteur, par exemple /usr/local/bin/php /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now.
Pourquoi un intervalle d’une minute recrée le problème initial
Il s’agit de la troisième défaillance. * * * * * semble plus sûr que cinq minutes, mais sur un site actif, il vous ramène au point de départ. Si une exécution dure plus longtemps que l’intervalle, l’exécution suivante démarre alors que la première est encore en cours. Dix minutes plus tard, dix processus PHP sont actifs, chacun conservant sa propre mémoire et sa propre connexion à la base de données.
Vérifiez directement l’accumulation des processus :
ps -eo etimes,user,args | grep '[c]ron event run'etimes correspond à l’âge du processus, en secondes. Une seule ligne est normale. Plusieurs lignes dont l’âge dépasse largement votre intervalle indiquent que les exécutions s’empilent. Sur un petit VPS, cela finit par provoquer une erreur MySQL Too many connections, ou par le kill de PHP par le kernel pour récupérer de la mémoire. Vous pouvez le confirmer avec sudo dmesg -T | grep -i 'killed process'.
WP-CLI exécute directement les callbacks des événements au lieu de demander wp-cron.php. Le verrou de 60 secondes utilisé par WordPress pour éviter les lancements en double ne s’applique donc pas ici. flock -n dans l’entrée de l’étape 3 empêche désormais les chevauchements. Une exécution ignorée se termine immédiatement et sans message, comme prévu.
Choisissez l’intervalle à partir de la planification la plus courte dont vous dépendez réellement, et mesurez d’abord la durée d’une exécution :
time sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-nowCinq minutes constituent une valeur par défaut raisonnable : un article programmé à 09:00 est publié au plus tard à 09:05. Quinze minutes conviennent à un site qui ne contient rien de critique en matière de délai. Une minute est réservée aux boutiques et aux plugins pilotés par une file d’attente qui en ont réellement besoin, et seulement après avoir vérifié qu’une exécution se termine en quelques secondes.
Si vous ne pouvez pas installer WP-CLI
Certains hébergeurs bloquent les outils shell. Une simple requête HTTP vers wp-cron.php exécute la même file d’attente, mais en passant par toute la pile web :
*/5 * * * * /usr/bin/curl -sS --max-time 120 "https://example.com/wp-cron.php?doing_wp_cron" > /dev/nullCe que vous perdez concrètement :
- L’exécution est limitée par les délais d’expiration du serveur web et de PHP-FPM. Une tâche longue peut donc être interrompue en cours d’exécution.
- Le certificat doit être valide, sinon
curls’arrête avecSSL certificate problem. Maintenez donc le renouvellement avec Certbot avec nginx. - Le cache de pages ne doit pas mettre
wp-cron.phpen cache. Sinon, les requêtes cron reçoivent une réponse mise en cache et rien ne s’exécute. - Vous n’obtenez aucune sortie par événement. La seule preuve qu’une tâche s’est exécutée est donc son effet.
-sS n’affiche rien en cas de succès avec curl, tout en affichant les erreurs. C’est le comportement attendu dans une tâche cron.
Ce qui doit encore figurer dans la planification du serveur
Une fois que cron gère la file d’attente de WordPress, regroupez au même endroit les autres tâches récurrentes du serveur, afin de pouvoir les consulter. Les correctifs de sécurité du système d’exploitation doivent être gérés par les mises à niveau automatiques, plutôt que par une ligne cron que vous maintenez manuellement. Les mises à jour des extensions et des thèmes WordPress répondent à une autre logique : wp plugin update --all dans une crontab peut facilement rendre un site en production inutilisable à 3 heures du matin, sans personne pour surveiller le résultat. Exécutez donc cette tâche volontairement, ou après une étape de staging et une sauvegarde.
FAQ
La désactivation de WP-Cron empêche-t-elle la publication des articles planifiés ?
Non, à condition qu’un autre mécanisme exécute la file d’attente. DISABLE_WP_CRON empêche uniquement le chargement des pages de déclencher la file d’attente. Les événements restent planifiés exactement comme avant. Un article prévu à 09:00 est publié lors de la première exécution de cron après 09:00. Avec un intervalle de cinq minutes, il est donc publié au plus tard à 09:05. Si vous définissez la constante sans jamais ajouter l’entrée cron, l’article reste dans la liste avec l’état Missed schedule jusqu’à ce qu’un processus exécute la file d’attente.
Quel utilisateur doit exécuter la tâche cron de WordPress ?
L’utilisateur qui possède les fichiers dans lesquels PHP écrit. Il s’agit de www-data sur une installation Ubuntu par défaut. Vérifiez-le avec stat -c '%U %G' /srv/www/example.com/wp-content/uploads, puis comparez le résultat à la ligne user = de la configuration de votre pool PHP-FPM. L’exécution de la tâche en tant que root fait s’arrêter WP-CLI avec une erreur YIKES. La forcer avec --allow-root crée des fichiers appartenant à root dans wp-content, que le serveur web ne pourra ensuite plus modifier.
À quelle fréquence system cron doit-il exécuter le cron de WordPress ?
Toutes les cinq minutes conviennent à la plupart des sites. Adaptez l’intervalle à la planification la plus courte dont vous dépendez réellement. Il doit aussi rester suffisamment supérieur à la durée d’une exécution, que vous pouvez mesurer en plaçant time devant la commande WP-CLI. Sur un site très sollicité, des intervalles d’une minute superposent les exécutions, sauf si flock les contrôle.
Pourquoi wp cron test échoue-t-il après la désactivation de WP-Cron ?
Parce que cette commande teste le lancement déclenché par les visiteurs. Elle signale une erreur lorsque DISABLE_WP_CRON vaut true. C’est le résultat attendu sur un serveur configuré de cette manière. Vérifiez plutôt le chemin du cron système. Consultez /var/log/wp-cron/example.log, ou planifiez un événement marqueur avec wp cron event schedule et vérifiez qu’il a disparu de wp cron event list après l’exécution suivante.
Ai-je besoin de WP-CLI, ou curl vers wp-cron.php suffit-il ?
Curl fonctionne. C’est la bonne solution lorsque vous ne pouvez pas installer WP-CLI. Il est plus lent, car il charge WordPress via le serveur web, et il est limité par le délai d’expiration de la requête. WP-CLI exécute les événements dans un processus PHP en ligne de commande, sans délai d’expiration web. Il affiche une ligne par événement avec sa durée. Le journal indique donc exactement ce qui a été exécuté et combien de temps l’opération a pris.