Kingsfordweg 151, 1043GR Amsterdam, The Netherlands ● 24/7 support — support@novogara.com
Knowledge base

Cron jobs and timers

The classic support ticket: “the script works when I run it, but the cron job does nothing.” There are four usual causes and they are all the same cause.

Why it works by hand and not on schedule

Cron gives your job an almost empty environment. Specifically:

  • PATH is minimal. Usually just /usr/bin:/bin. Use absolute paths for everything, including the interpreter.
  • No shell profile. Nothing from .bashrc or .profile is loaded, so your environment variables are gone.
  • The working directory is home, not where your script lives. cd first, or use absolute paths throughout.
  • A percent sign means newline in a crontab line. Escape it as \% — this bites every time somebody uses date +%Y-%m-%d.

Prove it to yourself by capturing the output:

*/5 * * * * /srv/app/job.sh >> /var/log/job.log 2>&1

Without that redirect, cron mails the output to the local user and you will never see it.

Reading the schedule

Five fields: minute, hour, day of month, month, day of week.

  • 0 3 * * * — every day at 03:00
  • */15 * * * * — every fifteen minutes
  • 0 4 * * 1 — Mondays at 04:00
  • 0 2 1 * * — the first of the month at 02:00

Do not schedule everything at exactly midnight. Every machine in the world runs its maintenance then; pick an odd minute and spread your own jobs out.

systemd timers, and why they are usually better

A timer pairs with a service unit, which means you get journald logging, dependency ordering, resource limits and the sandboxing from running your application as a service. Create backup.service with Type=oneshot, then backup.timer:

[Timer]
OnCalendar=daily
RandomizedDelaySec=1800
Persistent=true

Two properties cron does not have: RandomizedDelaySec spreads load automatically, and Persistent=true runs a missed job after the machine comes back up instead of silently skipping it.

systemctl list-timers shows every timer with its last and next run. There is no equivalent for cron.

Make failures visible

A job that fails silently is worse than no job. Three approaches, cheapest first:

  • Exit codes and journald. With a timer, systemctl --failed lists anything that did not finish cleanly.
  • A dead man's switch. Have the job call a monitoring URL on success; you get alerted when the call stops arriving. This catches the case where the job never ran at all.
  • Lock files. Use flock so a long run does not overlap with the next start: flock -n /var/lock/job.lock /srv/app/job.sh

Check the clock

Schedules depend on the machine agreeing with everyone else about the time. See time and NTP — and be aware of what your timezone does to a job scheduled at 02:30 on the night the clocks change.


Still stuck? Mail support@novogara.com — an engineer answers, at any hour. Back to the knowledge base