Cronjob instellen: planning, rechten en de meest voorkomende fouten

Gepubliceerd op 19 min leestijd

De job draait in de shell, maar niet in cron. Deze handleiding legt de vijf tijdvelden uit, het verschil tussen gebruikerscrontab en /etc/cron.d, de PATH-valkuil en hoe u aantoont dat een job echt is doorgelopen.

Een cronjob is in vijf minuten ingericht en kost daarna vaak uren. Het commando draait in de shell probleemloos, vanuit cron gebeurt er niets, en in het logboek staat hooguit dat cron iets heeft gestart. Dit artikel behandelt precies die punten waar de gebruikelijke handleidingen ophouden: de echte foutmeldingen, de verschillen tussen de distributies en de vraag waaraan u ziet dat een job werkelijk is doorgelopen en niet alleen is gestart.

Alle gegevens hebben betrekking op Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS en Ubuntu 22.04 LTS. Deze vier gebruiken dezelfde cron (de Debian-variant van Vixie Cron), niet cronie zoals Red Hat en Fedora. Dat verschil is verderop bij de tijdzone doorslaggevend.

Draait de cron-dienst eigenlijk wel?

In volledige installaties is cron aanwezig. In minimale cloud-images, container-basisimages en slanke netinst-installaties ontbreekt het pakket geregeld. Dat is de eerste vraag die u moet beantwoorden, voordat u ook maar iets gaat uitzoeken.

command -v crontab
systemctl status cron

Bewust command -v crontab en niet command -v cron: de dienst zelf staat in /usr/sbin, en die map zit op Debian alleen in het zoekpad van root. Als gewone gebruiker krijgt u daar simpelweg geen uitvoer te zien, hoewel cron geïnstalleerd is. Onder Ubuntu 24.04 werkt de opvraag ook als gebruiker zonder rootrechten. crontab staat daarentegen overal in /usr/bin en is daarmee voor iedereen zichtbaar, en systemctl status cron beantwoordt sowieso de belangrijkere vraag, namelijk of de dienst ook echt draait.

Ontbreekt de dienst, installeer hem dan alsnog:

apt-get update
apt-get install -y cron
systemctl enable --now cron

De dienst heet op Debian en Ubuntu cron, niet crond. Wie aan Red Hat gewend is, krijgt hier een Unit crond.service could not be found. en zoekt op de verkeerde plek.

In het normale bedrijf hoeft u niets te herstarten: de Debian-cron bewaakt de crontab-mappen via inotify en leest wijzigingen zelf in. Na het bewerken van een crontab hoeft u dus niets opnieuw te starten. Uitzonderingen zijn het wisselen van de systeemtijdzone en wijzigingen aan /etc/default/cron.

De vijf velden, en de valkuil in het vijfde

Elke regel begint met vijf tijdvelden, daarna volgt het commando.

VeldBereikOpmerking
Minuut0 tot 59
Uur0 tot 2324-uursnotatie, geen tijdzone
Dag van de maand1 tot 31
Maand1 tot 12ook jan tot dec
Weekdag0 tot 70 en 7 zijn allebei zondag, ook sun tot sat
30 4 * * *      /usr/local/bin/backup.sh      # dagelijks 04:30
*/10 * * * *    /usr/local/bin/check.sh       # elke 10 minuten
0 2 * * 0       /usr/local/bin/weekly.sh      # zondags 02:00
15 3 1 * *      /usr/local/bin/monthly.sh     # op de 1e om 03:15
0 9-17 * * 1-5  /usr/local/bin/business.sh    # op werkdagen elk uur van 9 tot 17

Twee details worden bijna altijd verkeerd begrepen.

Dag van de maand en weekdag vormen een OF, geen EN. Zodra beide velden beperkt zijn, dus geen van beide een sterretje bevat, draait de job wanneer één van de twee past. 0 3 13 * 5 betekent niet "vrijdag de 13e", maar "elke 13e en daarnaast elke vrijdag". Wilt u werkelijk vrijdag de 13e, dan controleert u dat in het script zelf.

Stapgroottes verdelen niet gelijkmatig. */7 * * * * draait op minuut 0, 7, 14, 21, 28, 35, 42, 49 en 56, daarna springt de teller naar het volgende uur. Tussen 56 en 0 liggen dus maar vier minuten. Dat geldt voor elk interval dat niet netjes opgaat in 60 respectievelijk 24. Voor "elke 90 minuten" bestaat er in cron geen nette schrijfwijze, hier is een systemd-timer de betere keuze.

crontab -e goed gebruiken

De gebruikerscrontab bewerkt u nooit rechtstreeks in /var/spool/cron/crontabs/, maar altijd via het bijbehorende gereedschap:

crontab -e

Bij de eerste aanroep meldt het programma no crontab for root - using an empty one. Op vers geïnstalleerde systemen volgt daarna een keuzemenu voor de editor. Ontbreekt elke editor, bijvoorbeeld in een slank image, dan breekt de aanroep af met een melding als /usr/bin/sensible-editor: 25: editor: not found. Oplossing: installeer een editor of stel de gewenste editor expliciet in.

apt-get install -y nano
EDITOR=nano crontab -e

Het grote voordeel van crontab -e boven het rechtstreeks schrijven van het bestand is de controle bij het opslaan. Is een regel stuk, dan ziet u:

"/tmp/crontab.7hK2mn/crontab":3: bad minute
errors in crontab file, can't install.
Do you want to retry the same edit? (y/n)

Het regelnummer klopt, het veld in de melding niet altijd: bad minute verschijnt ook wanneer er simpelweg één veld te weinig staat, omdat de parser dan alles naar links verschoven leest. Bij succes eindigt de handeling met crontab: installing new crontab. Alleen die regel betekent dat de wijziging is overgenomen.

Verdere commando's die u zou moeten kennen:

crontab -l                         # tonen
crontab -l > /root/crontab.bak     # veiligstellen
crontab -u www-data -l             # crontab van iemand anders lezen (als root)
crontab -i -r                      # verwijderen, met bevestiging (alleen aan de terminal)

Nog een opmerking over het veiligstellen: crontab -l > /root/crontab.bak meldt no crontab for root en levert retourwaarde 1 zolang er voor die gebruiker nog helemaal geen crontab bestaat. Het doelbestand ontstaat toch, dan met 0 byte. In de volgorde van dit artikel valt dat niet op, in een script dat de retourwaarde controleert of een oudere kopie overschrijft juist wel.

crontab -r zonder -i verwijdert de complete crontab meteen en zonder bevestiging. Omdat r en e op het toetsenbord dicht bij elkaar liggen, is dat een reëel geval van gegevensverlies. Went uzelf aan de terminal daarom crontab -i -r aan en maak vooraf een kopie met crontab -l.

Belangrijk is daarbij het woord terminal. crontab -i -r is een puur interactief commando. Hangt de standaardinvoer niet aan een terminal, dus in een script, in een cronjob of in een eenregelig commando als ssh host "crontab -i -r", dan blijft de bevestigingsvraag eindeloos rondgaan: crontab: really delete root's crontab? (y/n) Please enter Y or N: Please enter Y or N: ... herhaalt zich onbeperkt en schrijft in seconden honderden kilobytes uitvoer weg, en de aanroep eindigt alleen nog wanneer u hem van buitenaf afbreekt. Voor alles wat automatisch loopt neemt u daarom de niet-interactieve variant en maakt u vooraf een kopie:

crontab -l > /root/crontab.bak     # veiligstellen voordat er wordt verwijderd
crontab -r                         # verwijderen, zonder bevestiging
printf "y\n" | crontab -i -r       # bevestiging behouden, antwoord vooraf meegeven

Bestaan /etc/cron.allow of /etc/cron.deny, dan bepalen die twee bestanden wie er crontabs mag aanmaken. Betrokken gebruikers krijgen: You (username) are not allowed to use this program (crontab). Is /etc/cron.allow aanwezig, dan geldt dat bestand exclusief en is elke niet vermelde gebruiker geblokkeerd.

Gebruikerscrontab, /etc/crontab en /etc/cron.d

Er zijn vier plekken waar planningen kunnen staan, en die hebben verschillende formaten. Precies hier ontstaan de fouten die het lastigst te vinden zijn.

Gebruikerscrontab: vijf velden

Deze onderhoudt u via crontab -e, hij draait onder de gebruiker aan wie hij toebehoort en heeft geen gebruikersveld. Zet u er toch een in, dan probeert cron de gebruikersnaam als commando uit te voeren en vindt u in de mail of in het logboek:

/bin/sh: 1: root: not found

/etc/cron.d: zes velden

Bestanden in /etc/cron.d/ zijn systeemcrontabs en hebben tussen de tijdvelden en het commando een gebruikersveld. Dat is de juiste plek voor jobs die bij een toepassing of bij een configuratiebeheer horen, omdat elk bestand afzonderlijk vervangen kan worden.

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""

0 3 * * * root /usr/local/bin/backup.sh >> /var/log/kh-backup.log 2>&1

Vergeet u het gebruikersveld, dan leest cron het eerste woord van het commando als gebruikersnaam en mislukt de regel stilzwijgend, omdat die gebruiker niet bestaat.

Drie regels voor /etc/cron.d/ worden geregeld geschonden:

  • De bestandsnaam mag geen punt bevatten. Toegestaan zijn uitsluitend letters, cijfers, underscores en koppeltekens. backup.cron of kh-backup.sh worden zonder commentaar genegeerd. De reden is het pakketbeheer: zo worden restanten als .dpkg-dist of .dpkg-old nooit uitgevoerd. Noem het bestand gewoon kh-backup.
  • Rechten en eigenaar moeten kloppen. Verwacht worden root:root en modus 0644. Anders belanden er meldingen in het logboek als (*system*) WRONG FILE OWNER, (*system*) BAD FILE MODE of een verwijzing naar een onveilige modus die schrijfbaar is voor de groep of voor anderen. De job draait dan niet.
  • Het bestand moet op een regeleinde eindigen. cron eist dat elke vermelding met een newline wordt afgesloten. Eindigt de laatste regel zonder regeleinde, dan wordt die overgeslagen, en wel zonder enige melding: in de tegentest met een verder identiek bestand zonder afsluitend regeleinde draaide de job geen enkele keer, zonder foutmelding en zonder regel in het logboek. Wie het bestand aanmaakt met echo -n, met een printf zonder afsluitende \n of vanuit een sjabloon zonder lege regel aan het eind, verliest precies de laatste job.
chown root:root /etc/cron.d/kh-backup
chmod 0644 /etc/cron.d/kh-backup

/etc/crontab en de cron.*-mappen

/etc/crontab heeft eveneens zes velden en is eigendom van de distributie. Wijzig dat bestand alleen wanneer u weet waarom. Het roept via run-parts de mappen /etc/cron.hourly, cron.daily, cron.weekly en cron.monthly aan. Scripts daarin hebben het uitvoerbit nodig en mogen eveneens geen punt in de naam dragen. Een backup.sh in /etc/cron.daily/ wordt nooit uitgevoerd, een backup wel. Testen kunt u dat zonder iets uit te voeren:

run-parts --test /etc/cron.daily

Getoond worden alleen de scripts die run-parts werkelijk zou starten. Ontbreekt het uwe in de lijst, dan ligt het aan de naam of aan het uitvoerbit.

Waarom PATH in cron anders is

Dat is veruit de meest voorkomende oorzaak van "draait in de shell, maar niet in cron". cron start geen login-shell. Noch ~/.bashrc, noch ~/.profile, noch /etc/profile wordt gelezen. De Debian-cron zet voor gebruikerscrontabs een minimaal zoekpad:

PATH=/usr/bin:/bin

Daarmee ontbreken /usr/local/bin en /usr/sbin. Op Debian 12, Debian 13, Ubuntu 22.04 en Ubuntu 24.04 zijn /usr/bin en /usr/sbin nog steeds gescheiden mappen, de usr-merge betreft alleen /bin en /sbin. Alles wat u zelf in /usr/local/bin heeft gezet, alles uit pip install, alles uit een Node via nvm en systeemgereedschap als ufw of iptables bestaat voor cron eenvoudigweg niet. De foutmelding luidt dan:

/bin/sh: 1: backup.sh: not found

Het tweede deel van de valkuil: cron zet SHELL=/bin/sh. Op Debian en Ubuntu is /bin/sh een verwijzing naar dash, niet naar bash. Elk bash-idioom in de scriptkop of rechtstreeks in de crontab-regel mislukt:

/bin/sh: 1: [[: not found
/bin/sh: 1: source: not found
/bin/sh: 1: Syntax error: "(" unexpected

Drie tegenmaatregelen, in deze volgorde:

  1. Absolute paden gebruiken. /usr/local/bin/backup.sh in plaats van backup.sh, /usr/bin/php in plaats van php. Dat is de robuustste variant, omdat die onafhankelijk van elke omgevingsvariabele werkt. Lees het pad wel af in plaats van het uit uw hoofd in te tikken: command -v date levert op Debian 13, Debian 12, Ubuntu 24.04 en Ubuntu 22.04 /usr/bin/date op, op oudere systemen als Debian 11 daarentegen /bin/date. Een regel met een verkeerd pad neemt crontab zonder commentaar en zonder waarschuwing aan, hij mislukt pas tijdens de uitvoering en dan nog geruisloos ook. Voer ter controle één keer crontab -l uit en één keer env -i /bin/sh -c "/usr/bin/date", dat meldt een verkeerd pad meteen met not found.
  2. PATH en SHELL aan het begin van de crontab zetten. De toekenningen gelden voor alle volgende regels. Belangrijk: cron vult daarbij geen variabelen in, PATH=$PATH:/opt/bin werkt niet, schrijf het pad volledig uit.
  3. Een wrapper-script gebruiken. De crontab-regel roept alleen het script aan, het script zet zijn eigen omgeving.
#!/bin/bash
set -euo pipefail
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
cd /srv/app
exec ./do-the-work.sh

Wilt u weten wat cron uw job werkelijk meegeeft, laat het dan gewoon zien. Zet er voor één minuut in:

* * * * * /usr/bin/env > /tmp/cron-env.txt 2>&1

Alleen deze weg toont de omgeving die cron werkelijk zet. Een met env -i nagebouwde shell komt slechts in de buurt, omdat die zijn eigen standaardpad meebrengt. Daarna vergelijkt u /tmp/cron-env.txt met uw interactieve omgeving. Opvallend zijn meestal niet alleen PATH en SHELL, maar ook de ontbrekende locale-variabelen. Zonder LANG draait alles in de C-locale, wat de sorteervolgorde, de datumnotaties en de weergave van letters met diakritische tekens verandert. Scripts die op LANG=de_DE.UTF-8 bouwen, gedragen zich in cron anders. Ook de ssh-agent ontbreekt, waardoor jobs met SSH-toegang een sleutel zonder wachtwoordzin en een expliciete -i nodig hebben.

De laatste kleine gemeenheid in deze categorie: het procentteken. In een crontab-regel wordt een niet-ontsnapte % omgezet in een regeleinde, alles daarna gaat als standaardinvoer naar het commando. Datumnotaties moeten dus van een escape worden voorzien.

0 2 * * * /usr/bin/tar -czf /backup/web-$(date +\%F).tar.gz /var/www

Uitvoer omleiden, mails en "No MTA installed"

Standaard stuurt cron alles wat een job op stdout of stderr uitgeeft per mail naar de eigenaar van de crontab. Op een server zonder mailsysteem belandt dat in het logboek:

(CRON) info (No MTA installed, discarding output)

Dat is geen fout van de job. Het betekent alleen dat er uitvoer was en dat niemand die in ontvangst kon nemen. Een stille job levert deze regel niet op. Daarmee is het zelfs een nuttig signaal: duikt de regel plotseling op, dan is uw job iets gaan uitgeven, meestal een foutmelding.

Voor het omleiden zijn er drie zinvolle patronen:

# alles naar een logbestand, inclusief fouten
0 3 * * * /usr/local/bin/backup.sh >> /var/log/kh-backup.log 2>&1

# gewone uitvoer weg, fouten nog steeds per mail
0 3 * * * /usr/local/bin/backup.sh > /dev/null

# alles naar het journal, netjes gemarkeerd
0 3 * * * /usr/local/bin/backup.sh 2>&1 | /usr/bin/logger -t kh-backup

De variant > /dev/null 2>&1 is populair en gevaarlijk: die gooit ook alle foutmeldingen weg. Een job die al vier maanden mislukt, ziet er dan precies zo uit als een job die gewoon draait. Wilt u van de mails af, zet dan liever MAILTO="" aan het begin van de crontab en schrijf de uitvoer naar een bestand. Wilt u mails naar een bepaald adres, dan zet u MAILTO=alerts@example.org, maar daarvoor heeft u wel een geïnstalleerd mailsysteem nodig.

Eigen logbestanden in /var/log/ groeien onbeperkt door. Leg daarvoor een kleine regel aan in /etc/logrotate.d/, anders loopt uw schijf ooit vol door juist de best werkende cronjob.

Vaststellen of de job werkelijk heeft gedraaid

Hier scheidt de snelle handleiding zich van de betrouwbare controle. cron legt de start van een job vast. Het einde en de exitcode legt het niet vast. Een CMD-regel in het logboek bewijst dus alleen dat cron de shell heeft gestart, niet dat uw script geslaagd is.

De logboeken leest u per systeem anders uit:

journalctl -u cron --since "30 min ago"
journalctl -t CRON --since today

Op Debian 12 en Debian 13 is dat de enige weg, want sinds Bookworm wordt rsyslog niet meer standaard geïnstalleerd. Een bestand /var/log/syslog bestaat daar in de regel niet meer. Wie ernaar zoekt en het niet vindt, houdt de job ten onrechte voor niet gestart.

Op Ubuntu 22.04 en 24.04 is rsyslog meestal wel aanwezig, daar werkt daarnaast:

grep CRON /var/log/syslog

Een eigen /var/log/cron.log is er op geen van de vier versies af fabriek. De bijbehorende regel in /etc/rsyslog.d/50-default.conf staat uitgecommentarieerd. Zoek niet naar dat bestand, het bestaat alleen wanneer iemand het heeft ingeschakeld.

Een sluitend bewijs komt daarom uit de job zelf. Laat het script aan het einde een tijdstempel en de exitcode wegschrijven:

#!/bin/bash
set -euo pipefail
trap 'echo "$(date -Is) kh-backup beëindigd, exit $?" >> /var/log/kh-backup.log' EXIT
# het eigenlijke werk

Daarmee heeft u drie bewijzen in plaats van één: de CRON-regel in het journal (cron is gestart), de slotregel in uw logbestand (het script is tot het einde gekomen) en de exitcode (het is netjes afgesloten). Pas wanneer alle drie kloppen, werkt de job.

Kan een job langer draaien dan zijn interval, beveilig hem dan bovendien tegen overlap. Anders starten er ooit tien exemplaren tegelijk die de server onderuit trekken:

*/5 * * * * /usr/bin/flock -n /var/lock/kh-sync.lock /usr/local/bin/sync.sh

flock -n stopt meteen wanneer er al een exemplaar draait. Het gereedschap zit in util-linux en is op alle vier de systemen aanwezig.

@reboot en waarom systemd meestal beter is

Met @reboot laat u een commando bij het opstarten uitvoeren. Daarnaast bestaan @daily, @hourly, @weekly, @monthly en @yearly, die elk de vijf tijdvelden vervangen.

@reboot /usr/local/bin/start-app.sh

Dat oogt comfortabel en heeft drie serieuze zwaktes:

  • Het moment is niet "na het booten", maar "wanneer cron start". Of het netwerk, de database of een mount op dat moment gereed zijn, is puur een kwestie van geluk. De gebruikelijke noodgreep is er een sleep 30 voor te zetten, wat het probleem alleen verschuift.
  • Een herstart van de cron-dienst activeert @reboot opnieuw. Een systemctl restart cron, bijvoorbeeld na een pakketupdate, start uw toepassing een tweede keer, terwijl de eerste nog draait.
  • Er is geen bewaking. Geen exitcode, geen herstart na een crash, geen status.

Voor alles wat permanent moet draaien hoort een systemd-service, geen cronjob. Voor terugkerende taken is een timer de betere keuze. Twee bestanden volstaan:

[Unit]
Description=KernelHost Backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
[Unit]
Description=KernelHost Backup dagelijks

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=900

[Install]
WantedBy=timers.target

Geactiveerd wordt de timer, niet de service:

systemctl daemon-reload
systemctl enable --now kh-backup.timer
systemctl list-timers kh-backup.timer

De drie doorslaggevende voordelen ten opzichte van cron: Persistent=true haalt een gemiste draaibeurt na de volgende start alsnog in, cron slaat hem woordeloos over. De exitcode belandt in systemctl status kh-backup.service, u ziet dus zonder eigen logging of het gelukt is. En de complete uitvoer staat met journalctl -u kh-backup.service ter beschikking, zonder omleiding en zonder mail. Het zoekpad is bij systemd overigens ruimer dan bij cron en bevat standaard ook /usr/local/bin, maar zonder eigen toevoeging blijft het toch krap.

Voor starttaken vervangt u @reboot door een service met duidelijke afhankelijkheden:

[Unit]
After=network-online.target
Wants=network-online.target

Daarmee draait uw job gegarandeerd pas wanneer het netwerk werkelijk staat. Meer over units en hun opbouw vindt u onder een systemd-service aanmaken.

Tijdzone, UTC en de zomertijd

cron rekent altijd in de systeemtijdzone uit /etc/localtime. Op veel servers en in bijna alle cloud-images is dat UTC. Een job om 0 3 * * * draait dan in de zomer om 05:00 uur Midden-Europese zomertijd, niet om 03:00 uur. Controleer eerst waarmee u te maken heeft:

readlink -f /etc/localtime
timedatectl show -p Timezone --value
date
date -u

readlink -f /etc/localtime noemt het pad in /usr/share/zoneinfo en daarmee de werkelijk geldende zone, bijvoorbeeld /usr/share/zoneinfo/Etc/UTC of /usr/share/zoneinfo/Europe/Vienna. Het werkt op alle vier de versies en ook daar waar systemd niet draait. timedatectl show -p Timezone --value geeft dezelfde informatie kort en machineleesbaar weer, maar vereist wel systemd.

Wat u zich beter kunt afleren, is de blik in /etc/timezone. Debian 13 levert dat bestand niet meer mee, een cat /etc/timezone eindigt daar met cat: /etc/timezone: No such file or directory, en ook een installatie van tzdata brengt het niet terug. Op Debian 12, Ubuntu 24.04 en Ubuntu 22.04 bestaat het nog, het antwoord valt per versie dus anders uit. Doorslaggevend is in elk geval alleen de symlink /etc/localtime: een met de hand in /etc/timezone geschreven waarde verandert niets aan de systeemtijdzone en daarmee ook niets aan het moment waarop uw jobs afgaan.

De systeemtijdzone kunt u op elk moment omzetten, daarna kunt u cron beter opnieuw starten, zodat de dienst de wijziging zeker overneemt:

timedatectl set-timezone Europe/Vienna
systemctl restart cron

Op systemen zonder draaiend systemd zet u in plaats daarvan de symlink rechtstreeks, het resultaat is hetzelfde en laat zich meteen met readlink -f /etc/localtime nakijken:

ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime

Nu het punt dat in veel handleidingen verkeerd staat: de cron van Debian en Ubuntu kent geen CRON_TZ. Die variabele komt uit cronie, de cron van Red Hat, Fedora en AlmaLinux. Op Debian 13, Debian 12, Ubuntu 24.04 en Ubuntu 22.04 bestaat er geen tijdzone per gebruiker of per crontab. Zet u daar TZ=Europe/Vienna in de crontab, dan werkt dat uitsluitend op de omgeving van de uitgevoerde commando's, een date in het script toont dus de Weense tijd. Het moment waarop de job afgaat blijft daar volkomen onaangeroerd door en richt zich nog steeds naar de systeemtijdzone. Wie dat over het hoofd ziet, heeft een job die schijnbaar correct is ingesteld en toch twee uur ernaast draait.

Er blijven drie nette wegen over: de systeemtijdzone passend instellen, de tijden zelf naar UTC omrekenen, of overstappen op een systemd-timer, die een tijdzone rechtstreeks in de planning accepteert. Dat laat zich zonder risico controleren:

systemd-analyze calendar "Mon..Fri 03:00 Europe/Vienna"

De uitvoer noemt het eerstvolgende moment als concrete datum. Dat is de betrouwbaarste controle die u kunt uitvoeren voordat u de timer activeert.

Over de zomertijd nog een praktisch advies: plan geen jobs tussen 02:00 en 03:00 uur. In maart bestaat dat uur niet, in oktober bestaat het twee keer. Afhankelijk van de job betekent dat een uitgevallen of een dubbele draaibeurt, allebei één keer per jaar en allebei lastig te reproduceren. 01:30 of 03:30 zijn onopvallende alternatieven. Bij systemd-timers helpt daarnaast Persistent=true, zodat een uitgevallen draaibeurt wordt ingehaald.

Snel fouten opsporen in zeven stappen

Draait een cronjob niet, werk deze lijst dan op volgorde af. Hij dekt vrijwel alle gevallen.

  1. Draait de dienst? systemctl status cron. Geen dienst, geen job.
  2. Is de regel aangekomen? crontab -l voor gebruikersjobs, anders het bestand in /etc/cron.d/ bekijken. Controleer de bestandsnaam zonder punt, modus 0644, eigenaar root en het afsluitende regeleinde.
  3. Heeft cron hem eigenlijk wel gestart? journalctl -t CRON --since today. Ontbreekt de CMD-regel, dan is de planning of het bestand het probleem, niet het script.
  4. Klopt het aantal velden? Vijf velden in de gebruikerscrontab, zes in /etc/cron.d en /etc/crontab.
  5. Absolute paden? Elk commando en elk script met het volledige pad invoeren, en dat pad vooraf met command -v opzoeken in plaats van het in te tikken.
  6. Omgeving gecontroleerd? Eenmalig * * * * * /usr/bin/env > /tmp/cron-env.txt 2>&1 invoeren, een minuut wachten, vergelijken.
  7. In de cron-context testen. Voer het script niet in uw eigen shell uit, maar met een lege omgeving: env -i PATH=/usr/bin:/bin /bin/sh -c '/usr/local/bin/backup.sh'. Het zoekpad geeft u daarbij bewust mee. env -i /bin/sh alleen maakt de omgeving weliswaar leeg, maar dash zet daarna zijn eigen standaardpad /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, en dat is ruimer dan dat van cron. Juist de fouten waar het hier om gaat, zouden daarmee onopgemerkt blijven. Mislukt de aanroep, dan heeft u de oorzaak gevonden, zonder op het volgende uitvoermoment te hoeven wachten.

Het laatste punt is het waardevolst. Bijna elke cronjob die "onverklaarbaar" niet werkt, mislukt reproduceerbaar zodra u hem met een lege omgeving start. Daarmee wordt een raadsel dat zich maar eens per 24 uur voordoet een fout die u in tien seconden verhelpt.

Wie regelmatig back-ups via een cronjob draait, moet daarnaast ook de bestemming ervan beveiligen. Passende aanwijzingen staan in ons artikel over het beveiligen van een Linux-server.

Veelgestelde vragen

Waarom draait mijn script wel in de shell, maar niet als cronjob?
In de overgrote meerderheid van de gevallen ligt het aan de omgeving. cron start geen login-shell en leest dus noch ~/.bashrc noch /etc/profile, en voor gebruikerscrontabs zet het alleen PATH=/usr/bin:/bin. Daarmee ontbreken /usr/local/bin en /usr/sbin. Bovendien staat SHELL op /bin/sh, wat op Debian en Ubuntu dash is en geen bash-syntaxis begrijpt. Test de job in precies die omgeving: env -i PATH=/usr/bin:/bin /bin/sh -c, gevolgd door het scriptpad tussen enkele aanhalingstekens ('/pad/naar/script.sh'), dan mislukt hij meteen en reproduceerbaar. Het zoekpad geeft u daarbij uitdrukkelijk mee, want een kaal env -i /bin/sh zet het ruimere standaardpad van dash en verbergt juist deze fout.
Wat betekent de melding No MTA installed, discarding output?
Uw job heeft iets op stdout of stderr geschreven, cron wilde dat per mail bezorgen, maar er is geen mailsysteem geïnstalleerd. De job zelf heeft daar geen last van en kan gewoon geslaagd zijn. Leid de uitvoer om naar een logbestand, of zet MAILTO="" aan het begin van de crontab. Let op: de melding verschijnt vaak pas wanneer een tot dan toe stille job fouten begint te produceren.
Ondersteunen Debian of Ubuntu de variabele CRON_TZ?
Nee. CRON_TZ komt uit cronie, de cron van Red Hat en Fedora. Debian 13, Debian 12, Ubuntu 24.04 en Ubuntu 22.04 gebruiken de Debian-variant van Vixie Cron en kennen geen tijdzone per crontab. Een TZ= in de crontab werkt alleen op de omgeving van de commando's, niet op het moment waarop de job afgaat. Stel ofwel de systeemtijdzone in met timedatectl, reken de tijden zelf om naar UTC, of gebruik een systemd-timer, die een tijdzone in de OnCalendar-expressie accepteert.
Waarom wordt mijn bestand in /etc/cron.d genegeerd?
Bijna altijd vanwege de bestandsnaam. Toegestaan zijn alleen letters, cijfers, underscores en koppeltekens, een punt maakt het bestand onzichtbaar voor cron. backup.cron wordt dus genegeerd, backup niet. Andere oorzaken: verkeerde rechten (nodig zijn root:root en 0644), een ontbrekend gebruikersveld tussen de tijdvelden en het commando, of een laatste regel zonder afsluitend regeleinde.
Waar vind ik de cron-logboeken op Debian 12 en 13?
Via journalctl, want sinds Debian 12 wordt rsyslog niet meer standaard geïnstalleerd en bestaat een bestand /var/log/syslog daar meestal niet. Gebruik journalctl -u cron of journalctl -t CRON. Op Ubuntu 22.04 en 24.04 werkt daarnaast grep CRON /var/log/syslog. Een eigen /var/log/cron.log is er op geen van de vier systemen af fabriek.
Bewijst een CMD-regel in het logboek dat de job geslaagd is?
Nee. cron legt alleen de start van een job vast, niet het einde en niet de exitcode. Een script kan een seconde later gecrasht zijn en de regel in het logboek ziet er precies hetzelfde uit. Voor een echt bewijs laat u het script zelf een tijdstempel en de exitcode naar een logbestand schrijven, bijvoorbeeld via een trap op EXIT, of stapt u over op een systemd-timer, waarbij systemctl status de exitcode toont.
Moet ik @reboot of een systemd-service gebruiken?
In vrijwel alle gevallen een systemd-service. @reboot draait niet na het booten, maar bij de start van de cron-dienst, zonder garantie dat het netwerk of de database gereed is. Bovendien wordt het bij elke systemctl restart cron opnieuw geactiveerd, bijvoorbeeld na een pakketupdate. Een service met After=network-online.target en Wants=network-online.target lost beide problemen op en levert daarbij ook statusweergave en exitcode.

Cron Crontab systemd Linux-beheer Debian Ubuntu Automatisering Serverbeheer