Skip to main content
Back to module
Linux Administration · Automation and Access

Scheduling jobs

Reading1h 3mLesson 3 of 3 in this module

Schedule your backup script with cron, log its output, and know when a systemd timer is the better choice.

Why it matters

A backup script that someone has to remember to run will eventually not be run. Servers rely on scheduled jobs for backups, log clean-up, certificate renewal, report generation and health checks. cron is the classic Linux scheduler and is available on nearly every system; systemd timers are the modern alternative. Knowing both, and knowing how to tell whether a scheduled job actually ran, is a core admin skill.

How cron works

The cron service wakes up every minute, reads the schedules (crontabs) and runs any job whose time matches. Each user has their own crontab, and jobs run as that user.

crontab -l        # list your jobs
crontab -e        # edit your jobs (opens an editor; choose nano if asked)
crontab -r        # remove ALL your jobs: be careful, there is no undo

System-wide jobs live in /etc/crontab, /etc/cron.d/, and the folders /etc/cron.daily/, /etc/cron.weekly/ and so on, where packages drop their own maintenance scripts.

The five time fields

┌───────── minute (0-59)
│ ┌─────── hour (0-23)
│ │ ┌───── day of month (1-31)
│ │ │ ┌─── month (1-12)
│ │ │ │ ┌─ day of week (0-7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * *  command to run

* means "every". , lists values, - makes a range and / sets a step.

ScheduleMeaning
30 2 * * *every day at 02:30
*/15 * * * *every 15 minutes
0 9 * * 1-509:00 Monday to Friday
0 0 1 * *midnight on the first of each month
@rebootonce, when the system starts
@dailyonce a day (same as 0 0 * * *)

Times use the server's time zone (check with timedatectl), and many servers run in UTC. A site such as crontab.guru explains any expression in plain English if you want to double-check one.

The cron environment trap

cron does not run your jobs in the same environment as your terminal. Its PATH is minimal (often /usr/bin:/bin), your shell aliases and profile are not loaded, and the working directory is your home folder. That is why "it works when I run it, but not from cron" is so common. The fixes:

  • Use absolute paths for the script and for anything it calls.
  • Set variables at the top of the crontab if needed: PATH=/usr/local/bin:/usr/bin:/bin.
  • Make the script itself robust (shebang, set -euo pipefail, checks), as in the previous lesson.

Capture the output

By default cron tries to email a job's output to the user, which usually goes nowhere on a server. Redirect it to a log file instead:

30 2 * * * /home/alex/bin/backup.sh /home/alex/projects /home/alex/backups >> /home/alex/backup.log 2>&1

>> appends standard output to the log, and 2>&1 sends errors to the same place. Because the script exits non-zero on failure and prints "ERROR: ...", the log tells you exactly what went wrong.

Did it run?

grep CRON /var/log/syslog | tail          # Ubuntu: cron records each job it starts
journalctl -u cron --since today          # the same via the journal (unit "crond" on Red Hat)
tail /home/alex/backup.log                # your job's own output
ls -lt /home/alex/backups | head -3       # and the result itself

The syslog entry proves cron started the job. Only your log and the result prove it worked. For important jobs, monitor the result: alert if the newest backup is older than a day.

Worked example: test with a short interval first

Do not wait until 02:30 to find out a job is broken. Schedule it every two minutes, watch it run, then change it to the real time.

*/2 * * * * /home/alex/bin/backup.sh /home/alex/projects /home/alex/backups >> /home/alex/backup.log 2>&1

After a few minutes, check the log and the backups folder. When it works, crontab -e again and change */2 * * * * to 30 2 * * *.

Avoid overlapping runs

If a job can take longer than its interval, two copies can run at once. flock prevents that:

*/5 * * * * /usr/bin/flock -n /tmp/backup.lock /home/alex/bin/backup.sh /home/alex/projects >> /home/alex/backup.log 2>&1

systemd timers, in brief

A timer unit triggers a service unit on a schedule. Advantages: output goes to the journal automatically (journalctl -u backup), Persistent=true runs a job that was missed while the machine was off, and systemctl list-timers shows when each timer last ran and will next run.

# /etc/systemd/system/backup.timer
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true

[Install]
WantedBy=timers.target

It needs a matching backup.service with ExecStart= pointing at the script, then sudo systemctl daemon-reload and sudo systemctl enable --now backup.timer. Use cron for simple per-user jobs; reach for timers when you want journald logging, missed-run catch-up or dependencies on other services.

Common mistakes

  • Relative paths or commands not on cron's PATH.
  • No output redirection, so failures vanish silently.
  • Confusing the fields, for example * 2 * * *, which runs every minute from 02:00 to 02:59.
  • Forgetting the time zone of the server.
  • Running crontab -r when you meant crontab -e. Keep a copy: crontab -l > ~/crontab.backup.
  • Only checking that cron started the job, never that it succeeded.

Recap

  • cron runs jobs from each user's crontab on a five-field schedule (minute hour day month weekday).
  • Use absolute paths, redirect output with >> log 2>&1, and test with a short interval first.
  • Check grep CRON /var/log/syslog or journalctl -u cron, your log file and the result.
  • systemd timers add journald logging, catch-up of missed runs and systemctl list-timers.

Try it

The Do tab has you schedule your backup script, prove it ran, and move it to a real nightly time.

Also in this lesson: