Pourquoi automatiser les tâches sur un VPS ?
Un VPS est un serveur qui tourne en continu, souvent sans supervision humaine directe. C'est précisément pour cela que l'automatisation des tâches récurrentes est fondamentale : personne ne sera là à 3 h du matin pour lancer la sauvegarde ou renouveler le certificat Let's Encrypt. Une tâche oubliée peut se traduire par une perte de données, un certificat expiré qui met le site hors ligne, ou un disque saturé faute de rotation des logs.
En pratique, cinq catégories de tâches reviennent sur tout VPS Linux sérieux : la rotation des logs Nginx (logrotate ou un script dédié) qui empêche /var/log/nginx/ d'atteindre plusieurs gigaoctets en quelques semaines ; le nettoyage des images Docker orphelines (docker image prune -af) pour récupérer de l'espace disque sans intervention manuelle ; la sauvegarde PostgreSQL (pg_dump vers un stockage distant) qui garantit un RPO acceptable en cas de défaillance ; la vérification du certificat SSL avant expiration, pour déclencher un renouvellement certbot plutôt que de découvrir un site en HTTPS cassé ; et enfin les scripts de scraping léger ou d'indexation planifiée qui doivent s'exécuter à heure fixe. Les deux grands outils disponibles sur Linux — cron et systemd timers — permettent de planifier ces opérations de façon fiable.
Cas d'usage typiques sur un VPS
- Sauvegardes — dump MySQL ou PostgreSQL vers un stockage distant (
pg_dump -Fc mydb | gzip > /backup/mydb_$(date +%F).sql.gz), archivage de fichiers avecrsyncourclonevers un bucket S3 - Renouvellement de certificats TLS —
certbot renew --quietouacme.sh --cronplanifiés deux fois par jour, avec rechargement automatique de Nginx après succès - Rotation et purge des logs — suppression des fichiers de plus de 30 jours (
find /var/log -mtime +30 -delete), compression hebdomadaire, vidage des logs applicatifs en prod - Nettoyage Docker —
docker image prune -af, suppression des volumes orphelins, rotation des logs de conteneurs pour libérer/var/lib/docker - Sondes de santé — vérification qu'un service écoute (
curl -sf http://localhost:3000/health), redémarrage automatique si besoin, alerte par webhook - Synchronisation de données —
rsyncvers un second nœud, mise à jour de caches, synchronisation de fichiers statiques vers un CDN - Tâches applicatives planifiées — envoi d'emails de rappel, calcul de rapports périodiques, indexation de contenu, scripts de scraping léger
cron : rappel rapide
Cron est présent sur quasiment tous les systèmes Linux. Pour planifier une tâche, on édite la crontab de l'utilisateur courant avec crontab -e. La syntaxe repose sur cinq champs séparés par des espaces, suivis de la commande :
# ┌── minute (0-59)
# │ ┌── heure (0-23)
# │ │ ┌── jour du mois (1-31)
# │ │ │ ┌── mois (1-12)
# │ │ │ │ ┌── jour de la semaine (0-7, 0=dimanche)
# │ │ │ │ │
0 3 * * * /usr/local/bin/backup.shLes raccourcis @daily, @weekly, @monthly et @reboot simplifient les cas courants. Les fichiers déposés dans /etc/cron.d/ appartiennent au système et peuvent spécifier l'utilisateur d'exécution directement dans la ligne. Pour consulter les exécutions passées, deux chemins : /var/log/syslog (Debian/Ubuntu) filtré avec grep CRON /var/log/syslog, ou journalctl -t CRON sur les systèmes avec journald. Un point de vigilance souvent sous-estimé : le PATH par défaut de cron est minimaliste — /usr/bin:/bin — ce qui force à utiliser des chemins absolus dans les scripts, ou à redéfinir PATH= en tête de crontab. La variable MAILTO contrôle l'envoi des sorties par mail ; la mettre à vide (MAILTO="") désactive le mail silencieusement. Cron ne conserve aucun historique natif des exécutions passées et ne gère pas les tâches manquées si la machine était éteinte.
systemd timers : introduction
Un timer systemd est une paire de deux fichiers unitaires : un fichier .service qui décrit quoi exécuter, et un fichier .timer qui décrit quand le lancer. Le timer est activé par systemctl enable --now monservice.timer et piloté par le gestionnaire de services, exactement comme n'importe quel service systemd.
La directive OnCalendar= accepte une syntaxe riche et lisible :
- daily — tous les jours à minuit
- Mon *-*-* 03:00:00 — chaque lundi à 3 h
- *-*-* 02:30:00 — tous les jours à 2 h 30
- *:0/15 — toutes les 15 minutes
La directive AccuracySec= (défaut : 1 minute) permet à systemd de grouper les timers proches pour économiser des réveil processeur — sur un serveur mutualisé ou basse consommation, réduire cette valeur à 1s garantit l'exactitude de l'heure de déclenchement. La directive Persistent=true dans le bloc [Timer] est la fonctionnalité qui change tout par rapport à cron : si la machine était éteinte à l'heure prévue, le timer rattrape l'exécution manquée au prochain démarrage. Toutes les sorties passent par journald et sont consultables via journalctl -u monservice.service. La commande systemctl list-timers affiche la liste de tous les timers actifs, leur prochaine échéance et la date de leur dernière activation.
cron vs systemd timers : tableau comparatif
Faites défiler le tableau
| Critère | cron | systemd timer |
|---|---|---|
| Syntaxe de planification | 5 champs `* * * * *` | `OnCalendar=` lisible (`daily`, `Mon 03:00`) |
| Logs | Sortie mail ou redirection manuelle, `/var/log/syslog` | journald natif, `journalctl -u` |
| Tâches manquées (machine éteinte) | Perdues (sauf `anacron`) | `Persistent=true` rejoue après redémarrage |
| Dépendances inter-services | Aucune | `After=`, `Requires=`, `Wants=` |
| Isolation et ressources | Hérite de l'env shell, PATH minimaliste | Cgroups, `MemoryMax=`, `CPUQuota=` |
| Notification d'échec | Aucune en natif | `OnFailure=notify.service` déclenche une alerte |
| Exécution en tant qu'utilisateur précis | Champ user dans `/etc/cron.d/` | `User=` et `Group=` dans le `.service` |
| Débogage | Difficile sans log | `systemctl status`, `journalctl -xe` |
| Disponibilité | Tous systèmes Unix (containers, BSD, Alpine) | Systèmes avec systemd (Debian, Ubuntu, RHEL…) |
Quand conserver cron ?
Cron reste le bon choix dans plusieurs situations concrètes. Sur des systèmes anciens ou minimalistes sans systemd — certains conteneurs Docker légers, images Alpine, systèmes BSD — cron est souvent le seul outil disponible et il n'y a aucune raison d'en sortir. Dans un environnement multi-utilisateurs où chaque développeur gère ses propres tâches via crontab -e sans droits root, cron est plus simple à déléguer : créer un .service systemd exige des droits sur /etc/systemd/system/.
Pour des scripts one-liner très simples — un certbot renew hebdomadaire, un rsync nocturne sans dépendance — la crontab reste lisible et maintenable sans multiplier les fichiers. Cron est aussi le bon choix pour l'onboarding d'une équipe non-Linux : la syntaxe des cinq champs, documentée partout, est plus accessible qu'une unité systemd pour quelqu'un qui découvre l'administration système. Enfin, si vos tâches cron fonctionnent depuis des années sans problème, la migration n'apporte pas de valeur immédiate. Le critère décisif est la nécessité de logs structurés, de dépendances inter-services ou de contraintes de ressources — si vous n'en avez pas besoin, cron fait le travail.
Quand passer aux systemd timers ?
Les timers systemd s'imposent dès que la tâche dépasse le cadre d'une simple commande isolée. Vous avez besoin de journald pour tracer chaque exécution, ses sorties et son code de retour sans plomberie manuelle ? Systemd timers. Votre script de sauvegarde doit s'exécuter seulement après que PostgreSQL soit prêt, en déclarant After=postgresql.service dans le bloc [Unit] ? Systemd timers. Vous voulez limiter la mémoire d'un job de synchronisation pour qu'il n'étouffe pas les autres processus (MemoryMax=512M) ? Systemd timers.
Les dépendances inter-services sont l'argument le plus fort : un backup lancé alors que la base n'est pas encore disponible après un redémarrage produit un fichier vide sans erreur visible sous cron. Systemd garantit l'ordre. Le logging intégré via journald supprime tout le bruit habituel des redirections manuelles — journalctl -u backup.service --since yesterday donne l'historique complet avec codes de retour. La gestion des erreurs avec OnFailure= permet de déclencher une notification (email, webhook) sans polluer le script lui-même. Et si le serveur redémarre à 2 h 58 alors que la sauvegarde de 3 h aurait dû tourner, Persistent=true garantit qu'elle s'exécutera au prochain démarrage — ce que cron ne fait pas sans anacron.
Créer un timer systemd from scratch (exemple : sauvegarde quotidienne)
Créer le fichier service
Ouvrez
/etc/systemd/system/backup.serviceet saisissez :[Unit] Description=Sauvegarde quotidienne PostgreSQL After=postgresql.service [Service] Type=oneshot User=postgres ExecStart=/usr/local/bin/backup.shLe type
oneshotindique que le service se termine après l'exécution du script — c'est le type standard pour les tâches planifiées.Créer le fichier timer
Créez
/etc/systemd/system/backup.timeravec :[Unit] Description=Déclencheur quotidien pour la sauvegarde [Timer] OnCalendar=*-*-* 03:00:00 Persistent=true [Install] WantedBy=timers.targetPersistent=truegarantit que si la machine était éteinte à 3 h, la sauvegarde s'exécutera dès le prochain démarrage.Recharger systemd et activer le timer
Exécutez
systemctl daemon-reloadpour que systemd prenne en compte les nouveaux fichiers unitaires, puis activez et démarrez le timer en une seule commande :systemctl enable --now backup.timerVérifier avec systemctl status
Vérifiez l'état du timer avec
systemctl status backup.timer— la sortie doit indiqueractive (waiting)et afficher la prochaine date de déclenchement. Consultez aussi l'état du service après une première exécution avecsystemctl status backup.service.Lister tous les timers actifs
La commande
systemctl list-timers --allaffiche un tableau de tous les timers du système avec trois colonnes clés :NEXT(prochaine exécution),LEFT(temps restant) etLAST(dernière exécution). C'est votre tableau de bord pour vérifier que vos timers se déclenchent comme prévu.Consulter les logs
Accédez à toutes les sorties du service avec
journalctl -u backup.servicepour l'historique complet, oujournalctl -u backup.service -n 50 --since todaypour les 50 dernières lignes du jour. Le code de retour du script est visible dans la sortie journald.Ajouter une notification d'erreur (optionnel)
Pour être alerté en cas d'échec, ajoutez dans le bloc
[Unit]du fichier.service:OnFailure=notify-failure@%n.serviceCe service peut envoyer un email, un webhook Slack ou un ping ntfy selon votre infrastructure. C'est ce que cron ne peut pas faire nativement sans plomberie dans le script lui-même.
Tester manuellement le service
Avant d'attendre le déclenchement planifié, testez l'exécution immédiate avec
systemctl start backup.service(sans.timer). Consultez le résultat immédiatement avecjournalctl -u backup.service -n 20. Si le script réussit, la mise en place est validée.
Migrer une tâche cron existante vers systemd
Identifier la tâche cron à migrer
Listez vos crontabs avec
crontab -l(utilisateur courant) etcat /etc/cron.d/*(système). Notez la commande exacte, l'utilisateur d'exécution et le planning en cinq champs. Par exemple :30 2 * * 1 root /usr/bin/certbot renew --quietsignifie chaque lundi à 2 h 30 en tant que root.Traduire le planning en OnCalendar
Le format
OnCalendar=de systemd s'écritDayOfWeek Year-Month-Day Hour:Minute:Second.30 2 * * 1devientMon *-*-* 02:30:00.Pour valider la traduction avant de l'appliquer, utilisez
systemd-analyze calendar:systemd-analyze calendar 'Mon *-*-* 02:30:00'La commande affiche les 10 prochaines occurrences calculées, ce qui élimine toute ambiguïté sur le comportement réel.
Créer les fichiers .service et .timer
Créez
/etc/systemd/system/certbot-renew.serviceavecType=oneshot,ExecStart=/usr/bin/certbot renew --quiet, etUser=root. Créez ensuite/etc/systemd/system/certbot-renew.timeravecOnCalendar=Mon *-*-* 02:30:00etPersistent=true. Exécutezsystemctl daemon-reload && systemctl enable --now certbot-renew.timer.Tester le service avant de désactiver cron
Testez immédiatement l'exécution avec
systemctl start certbot-renew.serviceet consultez le résultat viajournalctl -u certbot-renew.service -n 20. Vérifiez que le timer apparaît danssystemctl list-timersavec une prochaine date cohérente. Ne désactivez la ligne cron qu'après validation.Désactiver la ligne cron
Une fois le timer validé, commentez ou supprimez la ligne dans la crontab d'origine. Pour un fichier dans
/etc/cron.d/, renommez-le avec l'extension.disabledou supprimez-le. Laissez cohabiter les deux pendant quelques jours si la tâche est critique — cron et systemd timers n'interfèrent pas.
Valider et déboguer vos expressions OnCalendar
Avant d'activer un timer en production, validez toujours votre expression OnCalendar= avec systemd-analyze calendar :
systemd-analyze calendar "daily"
systemd-analyze calendar "Mon *-*-* 02:30:00"
systemd-analyze calendar "*:0/15"Chaque commande affiche la prochaine occurrence et les dix suivantes — ce qui rend immédiatement visible une expression mal formée ou un décalage d'heure inattendu. Pour un aperçu de tous vos timers actifs avec leur date de prochain déclenchement et leur temps restant, systemctl list-timers --all est votre tableau de bord opérationnel quotidien.
Dépannage courant des timers systemd
Cinq problèmes reviennent régulièrement lors de la mise en place des timers.
1. "Unit not found" au systemctl enable — le fichier .timer ou .service n'est pas dans /etc/systemd/system/ ou porte un nom incorrect. Vérifiez l'orthographe exacte avec ls /etc/systemd/system/*.timer. Un daemon-reload est obligatoire avant tout enable ou start sur un nouveau fichier.
2. Timer actif mais qui ne se déclenche jamais — systemctl list-timers affiche la colonne NEXT avec une date cohérente ? Si NEXT est vide ou dans le passé, c'est souvent Persistent=true oublié combiné à une machine qui n'a pas redémarré depuis la création du timer. Vérifiez aussi systemctl status backup.timer : active (waiting) est l'état normal.
3. ExecStart introuvable ou permission refusée — le PATH dans un .service est minimaliste comme dans cron. Utilisez toujours le chemin absolu dans ExecStart= (/usr/bin/certbot et non certbot). Vérifiez que le script est exécutable (chmod +x /usr/local/bin/backup.sh) et que l'utilisateur déclaré dans User= y a accès.
4. Permissions sur le script — si User=postgres mais que le script appartient à root sans bit d'exécution pour postgres, le service échouera avec un code 203/EXEC. Consultez journalctl -u backup.service -n 20 pour voir le message exact.
5. Conflit entre cron et systemd sur la même tâche — si vous migrez progressivement, assurez-vous de ne pas avoir les deux actifs en même temps pour une tâche destructrice (purge de logs, rotation de fichiers). Les deux s'exécuteront indépendamment et doubleront les opérations. La vérification est simple : crontab -l | grep backup et systemctl is-active backup.timer.
Conclusion
Cron et systemd timers coexistent sans problème sur le même serveur — vous n'avez pas à tout migrer d'un coup. Conservez cron pour vos tâches simples et vos scripts utilisateur existants, et adoptez les timers systemd pour les nouvelles automatisations qui bénéficient des logs journald, des dépendances inter-services ou de l'isolation par cgroups. La commande systemd-analyze calendar et systemctl list-timers sont vos alliés pour valider et surveiller vos planifications.