Un e-commerce découvre un lundi que aucune commande n'a été exportée vers l'ERP depuis douze jours. Le script cron tourne pourtant : entrée crontab correcte, fichier log daté de ce matin. En réalité, le job échoue à la connexion SFTP depuis un changement de clé — mais le développeur n'a pas redirigé stderr, personne ne surveille exit code, et le mutualisé ne notifie pas.
Les tâches cron sont invisibles par design. Elles s'exécutent la nuit, sans utilisateur devant l'écran. C'est précisément pourquoi elles exigent plus d'observabilité qu'une page web — pas moins.
Anatomy d'un cron de production
| Élément | Mauvaise pratique | Bonne pratique |
|---|---|---|
| Sortie | /dev/null | Log structuré + rotation |
| Échec | Ignoré | Alert si exit ≠ 0 |
| Durée | Non mesurée | Timeout + alerte dépassement |
| Concurrence | Double exécution | Verrou flock / Redis |
| Idempotence | Non | Rejouable sans double effet |
Un cron qui « tourne » sans preuve de succès métier n'a pas tourné — il a seulement consommé du CPU.
Mutualisé vs VPS : où tourne votre nuit ?
Mutualisé :
- Précision horaire faible ; chevauchement avec jobs voisins.
mail()du cron souvent désactivée ou spam filtré.- Pas de systemd timer avancé.
VPS / cloud :
- systemd timers avec
Persistent=true(rattrape manqué). - Logs journald centralisés.
- Workers dédiés pour jobs lourds.
Pour sync facturation ou stock, le mutualisé est un pari. Pour purge cache hebdomadaire, acceptable.
Pattern observabilité minimal
- Wrapper shell :
#!/bin/bash
set -euo pipefail
LOG=/var/log/jobs/export-erp.log
{
echo "=== $(date -Is) start ==="
/usr/bin/php /app/bin/export-erp.php
echo "=== $(date -Is) ok ==="
} >> "$LOG" 2>&1 || curl -fsS -m 10 --retry 3 https://hc.example/ping/xxx-fail
- Healthchecks.io / Cronitor — ping START et SUCCESS ; alerte si absent.
- Métrique durée — Grafana ou simple log parsé ; alerte si > 2× médiane.
- Runbook — lien dans alerte vers procédure manuelle.
Voir Crontab et verrouillage et Celery : empêcher une file de tâches de devenir une boîte noire.
Idempotence et verrous
Scénario classique : import produits dure 25 min, cron toutes les 15 min → deux imports corrompent la base.
Solutions :
- flock -n en début de script — skip si déjà en cours.
- Redis SET NX avec TTL > durée max job.
- PostgreSQL advisory lock pour jobs DB-centric.
Chaque job critique doit être rejouable : relancer sans doubler les écritures (UPSERT, marqueur batch_id).
Migration cron → queue
| Signal | Action |
|---|---|
| Job > 5 min | Worker + queue |
| Retry avec backoff | Celery / Messenger |
| Dépendances entre jobs | Orchestrateur (Temporal, Airflow light) |
| Visibilité équipe | Dashboard Flower / Horizon |
Le cron reste utile pour déclencher (0 2 * php bin/console messenger:consume) — pas pour tout exécuter inline.
Le sommet : l'échec cron se découvre toujours par le métier, jamais par la tech
L'hébergeur vend « cron inclus ». Votre responsabilité : prouver succès ou échec indépendamment du panel hosting.
Décider et avancer sans angle mort
- Inventairez tous les crons (crontab + panel + CI).
- Classez criticité et ajoutez healthcheck externe aux critiques.
- Verrouillez jobs > 1 min ou intervalle court.
- Testez échec volontaire — alerte arrive en < 5 min ?
- Documentez owner et runbook par job.
Comparez PaaS avec cron observable (Alwaysdata) via notre annuaire.
Questions fréquentes
Pourquoi les crons mutualisés sont-ils peu fiables ?
Fenêtres imprécises, charge partagée, pas de notification d'échec — risqué pour sync critique.
Comment savoir qu'un cron a échoué ?
Exit code loggé + ping externe ou alerte ; silence n'est pas succès.
Faut-il un verrou pour éviter les doublons ?
Oui si durée job ≥ intervalle — flock, Redis ou advisory lock.
Cron système ou queue ?
Cron pour court et rare ; queue + workers pour long, retry et visibilité.
Un cron fiable ne se voit pas la nuit — il se prouve le matin, ou alerte avant que le métier ne s'en aperçoive.