Een cron-expressie bestaat uit vijf velden, gescheiden door spaties: minuut, uur, dag van de maand, maand en dag van de week, gevolgd door het commando dat moet draaien. 30 2 * * * betekent “elke dag om 02:30”. Elk veld accepteert een getal, een * voor “elke waarde”, een lijst met komma’s, een bereik met een koppelteken of een stap met een schuine streep. De meeste cronproblemen komen niet door de syntax zelf, maar door alles eromheen: de tijdzone van de server, de regel voor het combineren van dag van de maand en dag van de week, en de minimale omgeving waarin cron commando’s uitvoert.
Deze gids legt elk veld en elk speciaal teken uit, geeft een tabel met kant-en-klare expressies en loopt de fouten langs waardoor jobs op het verkeerde moment draaien, of helemaal niet.
De vijf velden
Elke regel in een gebruikerscrontab heeft dezelfde opbouw:
┌───────────── minute (0–59)
│ ┌─────────── hour (0–23)
│ │ ┌───────── day of month (1–31)
│ │ │ ┌─────── month (1–12 or JAN–DEC)
│ │ │ │ ┌───── day of week (0–7 or SUN–SAT, 0 and 7 are both Sunday)
│ │ │ │ │
* * * * * /path/to/command
Een job draait wanneer de huidige minuut overeenkomt met alle velden, met één uitzondering voor de twee dagvelden die we hieronder bespreken. Maanden en weekdagen kun je ook schrijven als Engelse afkortingen van drie letters; hoofdletters maken daarbij niet uit.
Je eigen crontab bewerk je met crontab -e en bekijk je met crontab -l. Systeembrede bestanden, /etc/crontab en de bestanden in /etc/cron.d/, hebben een zesde veld tussen het schema en het commando: de gebruiker waaronder de job draait. Een regel van het ene formaat naar het andere kopiëren zonder dat veld toe te voegen of te verwijderen, is een klassieke manier om een job stuk te maken.
Speciale tekens
TekenBetekenisVoorbeeldResultaat*Elke waarde* * * * *Elke minuut,Lijst van waarden0 8,12,18 * * *Om 08:00, 12:00 en 18:00-Bereik0 9 * * 1-509:00 van maandag tot en met vrijdag/Stap*/15 * * * *Minuut 0, 15, 30 en 45
Hoe stappen echt werken
Een stap verdeelt het bereik van een veld, te beginnen bij de eerste waarde. */15 in het minuutveld betekent 0, 15, 30 en 45. Een stap kan ook op een bereik werken: 0-30/10 betekent minuut 0, 10, 20 en 30.
Stappen beginnen opnieuw bij de grens van het veld. */7 in het minuutveld draait op 0, 7, 14 en zo verder tot 56, en dan weer op 0, dus het laatste gat van elk uur is maar vier minuten. */7 is dus niet “elke zeven minuten”. Ook “elke 90 minuten” kun je in klassieke cron niet in één regel uitdrukken; daarvoor heb je twee regels nodig of een controle in het script.
Syntax die geen klassieke cron is
In documentatie zie je soms L (laatste), W (dichtstbijzijnde werkdag), # (n-de weekdag van de maand) en ?. Die komen uit Quartz en sommige cloud- en CI-schedulers, niet uit de cron op een Linux-server, en een deel van die schedulers voegt ook nog een secondenveld aan het begin toe. Veel cron-versies weigeren bovendien bereiken van namen, dus schrijf 1-5 in plaats van MON-FRI.
Snelkoppelingen zoals @daily
De meeste cron-implementaties op Linux accepteren een benoemde snelkoppeling in plaats van de vijf velden:
@reboot: één keer, wanneer de cron-daemon start na het opstarten.
@yearly of @annually: 0 0 1 1 *, middernacht op 1 januari.
@monthly: 0 0 1 * *, middernacht op de eerste van de maand.
@weekly: 0 0 * * 0, middernacht tussen zaterdag en zondag.
@daily of @midnight: 0 0 * * *.
@hourly: 0 * * * *.
Alle snelkoppelingen gaan af op het hele uur of om middernacht, dus op een drukke server strijden alle jobs om 00:00 om dezelfde resources. Schrijf je de volledige expressie met een oneven minuut, zoals 17 0 * * *, dan spreid je de belasting.
Kant-en-klare cron-expressies
ExpressieDraait* * * * *Elke minuut*/5 * * * *Elke 5 minuten*/15 * * * *Elke 15 minuten0 * * * *Elk uur, op het hele uur15 */2 * * *Op minuut 15 van elk tweede uur: 00:15, 02:15, 04:15 enzovoort0 9-17 * * 1-5Elk uur van 09:00 tot 17:00, maandag tot en met vrijdag*/10 8-18 * * 1-5Elke 10 minuten tijdens kantooruren op werkdagen30 2 * * *Elke dag om 02:300 8,12,18 * * *Drie keer per dag: 08:00, 12:00 en 18:000 6 * * 1Elke maandag om 06:000 22 * * 5Elke vrijdag om 22:000 0 * * 0Elke zondag om middernacht0 3 1 * *De eerste dag van elke maand om 03:000 4 1,15 * *De 1e en 15e van elke maand om 04:000 5 1 */3 *Per kwartaal: 1 januari, april, juli en oktober om 05:000 0 1 1 *Eén keer per jaar, om middernacht op 1 januari@rebootEén keer na elke systeemstart
Een veelgemaakte fout bij dit soort schema’s is * */2 * * *. Die draait niet elke twee uur, maar elke minuut tijdens elk tweede uur: zestig keer achter elkaar. Beperk je het uur, stel dan altijd ook de minuut in.
Dag van de maand en dag van de week: de OF-regel
Alle velden worden met EN gecombineerd, behalve de twee dagvelden. Zijn zowel dag van de maand als dag van de week beperkt, dus geen van beide is *, dan draait de job wanneer een van beide overeenkomt. Deze regel:
0 9 1 * 1 /usr/local/bin/report.sh
draait dus om 09:00 op de eerste van elke maand en om 09:00 op elke maandag, niet alleen op maandagen die op de eerste vallen. Om dezelfde reden betekent 0 9 1-7 * 1 niet “de eerste maandag van de maand”; die draait op dag 1 tot en met 7 én op elke maandag. Wil je de eerste maandag, plan dan dag 1 tot en met 7 in en controleer de weekdag in het commando. Let op het geëscapete procentteken, verderop uitgelegd:
0 9 1-7 * * [ “$(date +\%u)” = “1” ] && /usr/local/bin/report.sh
Klassieke cron kent ook geen “laatste dag van de maand”. Met GNU date plan je dag 28 tot en met 31 in en draai je alleen wanneer morgen de eerste is:
0 23 28-31 * * [ “$(date -d tomorrow +\%d)” = “01” ] && /usr/local/bin/month-end.sh
Tijdzones en zomertijd
Cron gebruikt de tijdzone van het systeem waarop het draait. Een job die voor Stockholmse tijd is geschreven, start op een server in UTC een of twee uur verkeerd, afhankelijk van het seizoen. Controleer de zone van de server met timedatectl of date voordat je een schema kiest. Gebruik dezelfde zone als je de job monitort: cronmonitors van Uptimecheck nemen het crontab-schema samen met een tijdzone, zodat de verwachte looptijden overeenkomen met die van de server.
Sommige implementaties, zoals cronie, accepteren een variabele CRON_TZ bovenaan de crontab om de schema’s in een andere zone te draaien. De ondersteuning verschilt per cron-versie, dus test het in plaats van ervan uit te gaan dat het werkt.
Zomertijd zorgt voor de vreemdste bugs. In het voorjaar bestaat er een lokaal uur niet; in het najaar komt een uur twee keer voor. Een job die ongeveer tussen 01:00 en 03:00 lokale tijd staat ingepland, kan worden overgeslagen, uitgesteld of twee keer draaien, en hoe elke cron-implementatie met de overgang omgaat, verschilt. De veiligste opties:
Laat servers op UTC draaien, dat geen zomertijd kent, en reken om in je hoofd of in een comment.
Moet de server op lokale tijd draaien, houd belangrijke jobs dan buiten de uren waarin de klok verspringt.
Zet de tijdzone in een comment boven elke job, zodat de volgende persoon niet hoeft te raden.
Zorg dat jobs veilig twee keer kunnen draaien, bijvoorbeeld door te controleren of de export van vandaag al bestaat.
Valkuilen met de omgeving en PATH
Een commando dat in je terminal werkt en onder cron mislukt, mislukt bijna altijd door de omgeving. Cron laadt je shellprofiel niet, dus veel wat je als vanzelfsprekend beschouwt, ontbreekt:
PATH is minimaal, vaak alleen /usr/bin:/bin. Commando’s in /usr/local/bin, of tools die je met nvm, pyenv, rbenv of Composer hebt geïnstalleerd, worden niet gevonden. Gebruik absolute paden of stel PATH bovenaan de crontab in.
De shell is /bin/sh, niet bash. Syntax die alleen in bash werkt, zoals [[ ]] of source, mislukt. Stel SHELL=/bin/bash in of zet de logica in een script met een bash-shebang.
Procenttekens zijn speciaal. In een crontab-regel wordt % omgezet in een nieuwe regel. date +%F moet je schrijven als date +\%F, of naar een script verplaatsen.
De werkmap is de homemap van de gebruiker, niet je project. Relatieve paden wijzen naar de verkeerde plek. Doe eerst een cd naar het project.
Omgevingsvariabelen zijn weg. API-sleutels en database-URL’s die je in .bashrc exporteert, bestaan niet. Laad ze in het script uit een bestand.
Output verdwijnt. Cron mailt de output naar MAILTO als mail is ingericht, en op de meeste servers is dat niet zo. Stuur de output door naar een logbestand.
Runs kunnen overlappen. Duurt een job die elke vijf minuten draait soms zeven minuten, dan lopen er twee tegelijk. flock -n slaat een run over zolang de vorige de lock vasthoudt.
Sommige cron-versies negeren ook een laatste regel die niet op een regeleinde eindigt. Een crontab die met dit alles rekening houdt, ziet er zo uit:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=””
# Nightly database backup. Server time is UTC.
30 2 * * * cd /var/www/fjordstudio && flock -n /tmp/backup.lock ./bin/backup.sh >> /var/log/backup.log 2>&1
Zo test je een cron-expressie
Lees hem terug in woorden. Plak de expressie in een cron-parser en controleer of de beschrijving en de eerstvolgende looptijden kloppen met wat je bedoelde.
Voer het commando uit in de omgeving van cron. Met een uitgeklede omgeving reproduceer je de meeste “werkt wel in mijn terminal”-fouten, zonder op het schema te hoeven wachten.
Plan hem één keer elke minuut in. Gebruik tijdelijk * * * * *, kijk of hij draait en stel daarna het echte schema in.
Controleer of cron hem heeft gestart. Op Debian en Ubuntu met grep CRON /var/log/syslog of journalctl -u cron; op systemen gebaseerd op Red Hat met /var/log/cron of journalctl -u crond.
Controleer of de crontab is geïnstalleerd met crontab -l, als de gebruiker die hem moet draaien.
Zo simuleer je de omgeving van cron vanuit een shell:
env -i SHELL=/bin/sh PATH=/usr/bin:/bin HOME=”$HOME” /bin/sh -c ‘/usr/local/bin/backup.sh’
Koppel elke job aan een heartbeat
Een correcte expressie vertelt cron alleen wanneer een job moet starten. Ze zegt niets over of de job ook echt heeft gedraaid of is afgerond. Een reboot, een overschreven crontab of een ontbrekende variabele mislukken allemaal geruisloos.
Een heartbeat dicht dat gat. De job roept een unieke URL aan zodra hij succesvol klaar is, en de monitoringdienst waarschuwt je wanneer dat verzoek niet op tijd binnenkomt:
30 2 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 –retry 3 https://heartbeat.example.com/your-unique-id > /dev/null
Stel het verwachte interval van de heartbeat in op hetzelfde schema als de cron-expressie, met marge voor trage runs: 24 uur plus een beetje voor een nachtelijke job, een uur plus een beetje voor een job die elk uur draait. Cronjob monitoring van Uptimecheck werkt zo, met een melding wanneer een ping uitblijft en een herstelmelding wanneer de job zich weer meldt. De cronmonitors nemen het crontab-schema in jouw tijdzone, accepteren start- en eindpings op één monitor, waarschuwen wanneer een run langer duurt dan de maximale looptijd en kunnen de exitcode en de output van de job vastleggen. De gids over cronjobs monitoren met heartbeats beschrijft de inrichting in detail, en gemiste crontaken voorkomen gaat over de gewoontes eromheen.
Blijft een heartbeat uit, dan ligt de oorzaak vaak op de server: een volle schijf, uitgeput geheugen of een onverwachte reboot. Met servermonitoring naast je heartbeats zie je die oorzaak op hetzelfde moment.
Checklist met veelgemaakte fouten
Het uur beperken, maar de minuut op * laten staan.
Beide dagvelden instellen en EN verwachten in plaats van OF.
Ervan uitgaan dat de server in jouw tijdzone staat.
Belangrijke jobs inplannen in het uur van de zomertijdovergang.
Relatieve paden, bash-syntax of een niet-geëscapete % gebruiken.
Een job draaien zonder heartbeat, zodat niemand merkt wanneer hij stopt.
Samenvatting
Vijf velden, vier speciale tekens en een handvol snelkoppelingen dekken bijna elk schema dat je nodig hebt. De expressie is zelden het probleem. Tijdzones, de OF-regel voor de dagvelden en de kale omgeving van cron zijn dat wel. Zet de tijdzone bij elke job, gebruik absolute paden, log de output en geef elke job die ertoe doet een heartbeat.
Heartbeats van Uptimecheck zitten in elk abonnement, ook in het Free-abonnement, naast uptime-, SSL-, domein- en DNS-monitoring. Start een gratis proefperiode van 14 dagen, zonder creditcard.