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.
| Schedule | Meaning |
|---|---|
30 2 * * * | every day at 02:30 |
*/15 * * * * | every 15 minutes |
0 9 * * 1-5 | 09:00 Monday to Friday |
0 0 1 * * | midnight on the first of each month |
@reboot | once, when the system starts |
@daily | once 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 -rwhen you meantcrontab -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/syslogorjournalctl -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.