Lancer une page web depuis la crontab
Une tâche planifiée est parfois écrite sous la forme d'une page web : certains frameworks et applications (WordPress, par exemple) ne s'initialisent correctement que dans un contexte web, avec leurs variables de serveur, leur configuration et leurs sessions. Plutôt que de recréer cet environnement en ligne de commande, on demande à la crontab d'appeler l'adresse de la page, exactement comme le ferait un navigateur.
Le but, dans cet article : exécuter la page https://www.monsite.com/monscript.php chaque nuit à minuit.
La commande avec curl ou wget
Deux outils font cet appel, installés sur la plupart des serveurs (which curl wget donne leur chemin) :
curl -fsS -m 300 -o /dev/null "https://www.monsite.com/monscript.php" wget -q -O /dev/null --tries=1 --timeout=300 "https://www.monsite.com/monscript.php"
| Option | Rôle |
|---|---|
curl -f | renvoie un code d'erreur si le serveur répond par une erreur HTTP (404, 500…) |
curl -s -S | pas de barre de progression, mais les erreurs restent affichées |
curl -m 300 | abandonne au bout de 300 secondes |
wget -q | mode silencieux |
wget -O /dev/null | jette la page reçue au lieu de créer un fichier |
wget --tries=1 | ne réessaie pas en cas d'échec (voir plus bas) |
wget --timeout=300 | abandonne au bout de 300 secondes sans réponse |
Attention à une erreur fréquente : wget -O -q. L'option -O attend un nom de fichier, donc -q est pris pour ce nom et la page est enregistrée dans un fichier appelé « -q ». Pour afficher la page au lieu de l'enregistrer, écrivez -q -O - (le tiret seul désigne la sortie standard).
Dans la crontab
Lancez l'édition de la crontab avec crontab -e, puis ajoutez la ligne, en remplaçant /usr/bin/curl par le chemin donné par which curl sur votre serveur :
0 0 * * * /usr/bin/curl -fsS -m 300 -o /dev/null "https://www.monsite.com/monscript.php"
Cette ligne appelle la page tous les jours à minuit. Pour choisir une autre fréquence, voir la page Crontab : exécutions planifiées sous Linux. Trois détails propres aux URL :
- mettez l'adresse entre guillemets : un
&non protégé, dans?a=1&b=2, lancerait la commande en arrière-plan et couperait l'adresse ; - dans une crontab, un
%s'écrit\%(par exemple%20devient\%20) ; - utilisez
https://: sinon le jeton éventuel et la réponse circulent en clair.
Pour garder une trace de la réponse de la page, redirigez-la vers un fichier au lieu de la jeter :
0 0 * * * /usr/bin/curl -fsS -m 300 "https://www.monsite.com/monscript.php" >> /var/log/monscript.log 2>&1
Éviter les mauvaises surprises
Les délais d'attente
Une page web n'a pas les mêmes limites qu'un script en ligne de commande. PHP arrête une page au bout de max_execution_time (30 secondes par défaut), et le serveur web coupe lui aussi les requêtes trop longues. Si le traitement est long, ajoutez en tête du script :
ignore_user_abort(true); // continue même si l'appelant se déconnecte set_time_limit(0); // pas de limite de durée de PHP
Le délai propre au serveur web (Timeout d'Apache, fastcgi_read_timeout de nginx) reste à vérifier. Si le traitement dépasse quelques minutes, c'est un signe qu'il vaut mieux le lancer en ligne de commande.
Les réessais de wget
Par défaut, wget attend la réponse pendant 15 minutes et, en cas de coupure ou de délai dépassé, il réessaie jusqu'à 20 fois. La page peut donc être exécutée plusieurs fois par erreur. C'est pourquoi les exemples utilisent --tries=1 et --timeout. Avec curl, -m fixe la durée maximale et aucun réessai n'a lieu.
Les exécutions qui se chevauchent
Si la page peut durer plus longtemps que l'intervalle entre deux appels, empêchez le lancement d'une seconde copie avec flock :
*/5 * * * * /usr/bin/flock -n /tmp/monscript.lock /usr/bin/curl -fsS -m 240 -o /dev/null "https://www.monsite.com/monscript.php"
Qui peut appeler la page ?
Une page web est accessible à tout le monde : un visiteur, ou un robot d'indexation, peut la lancer en tapant son adresse. Pour une page qui modifie des données, envoie des e-mails ou consomme des ressources, c'est un risque. Protégez-la en vérifiant un jeton secret envoyé par la crontab dans un en-tête (et non dans l'adresse, qui apparaît dans les journaux du serveur et dans la liste des processus) :
/usr/bin/curl -fsS -m 300 -o /dev/null -H "X-Cron-Token: une-longue-valeur-secrete" "https://www.monsite.com/monscript.php"
Côté PHP, au début de monscript.php :
<?php
$attendu = 'une-longue-valeur-secrete';
$recu = isset($_SERVER['HTTP_X_CRON_TOKEN']) ? $_SERVER['HTTP_X_CRON_TOKEN'] : '';
if (!hash_equals($attendu, $recu)) {
http_response_code(403);
exit('Accès refusé');
}
Rangez la valeur secrète dans un fichier de configuration hors du dossier public, pas dans le script lui-même. Vous pouvez aussi limiter l'accès à la page à l'adresse du serveur (127.0.0.1) dans la configuration d'Apache ou de nginx, et demander aux robots de ne pas l'indexer.
Quand préférer la ligne de commande ?
Appeler une page web est pratique mais indirect. Dès que le script n'a pas vraiment besoin du contexte web, ou qu'il est long, exécutez-le directement avec PHP :
0 0 * * * /usr/bin/php /home/admin/www/monscript.php
Il n'y a alors ni limite de durée imposée par le serveur web, ni page accessible depuis Internet, ni jeton à gérer. Beaucoup de frameworks proposent d'ailleurs une commande dédiée (la console de Symfony ou Laravel, wp-cli pour WordPress).
Pour aller plus loin
- Crontab : exécutions planifiées sous Linux : syntaxe, variables d'environnement, journaux et erreurs courantes.
- Linux : démarrage automatique des services