Every server has tasks to carry out at regular intervals: the nightly backup, cleaning up temporary files, renewing a certificate, sending a weekly report. On Linux there are two tools for this: the classic cron, around for decades, and systemd timers, more recent and richer. Both work well, provided you know their pitfalls.
Cron: the syntax
Cron reads crontabs, files in which each line describes a job with five time fields followed by the command:
# minute hour day-of-month month day-of-week command
30 2 * * * /usr/local/bin/backup.sh
0 */6 * * * /usr/local/bin/sync.sh
15 8 * * 1 /usr/local/bin/weekly-report.sh
The first line runs the backup every day at 2:30; the second every six hours on the hour; the third every Monday at 8:15. The crontab -e command edits the current user's crontab, while files in /etc/cron.d/ let you define system jobs, also specifying the user to run them as.
The pitfalls of cron
The environment is almost empty
The most common problem is a job that works when launched by hand and fails from cron. The reason is almost always the environment: cron runs commands with a minimal PATH and without the variables your shell loads at login. The solution is to use absolute paths for programs and files, or to define the necessary variables at the top of the script.
Output gets lost
By default cron tries to email the output of commands. On a server without mail configured, that output vanishes, error messages included. It is good practice to redirect output to a log file or, better, to the system log.
The percent character
In a crontab the % symbol has a special meaning and must be preceded by a backslash. A command such as date +%Y-%m-%d written directly in the crontab doesn't do what you expect. Moving the logic into a script eliminates the problem.
Jobs lost while the server is off
If the server is off at the scheduled time, cron does not catch up on the missed job. On a laptop or a machine that reboots often, a nightly backup can be skipped for days without anyone noticing.
systemd timers
On distributions that use systemd you can schedule a job with two files: a service, which describes what to run, and a timer, which describes when.
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=Runs the backup every night
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=10min
[Install]
WantedBy=timers.target
You enable it with systemctl enable --now backup.timer and check it with systemctl list-timers, which shows the last and next run of every timer.
What they offer in addition
- Persistent=true catches up on missed jobs: if the server was off at the scheduled time, the job starts as soon as it comes back on.
- Built-in logging: all output goes to the journal, readable with
journalctl -u backup.service. - No overlap: if the previous run is still in progress, the timer does not start a second instance of the same service.
- Random delay:
RandomizedDelaySecspreads out start times, useful when many servers would hit the same service at the same instant. - Resource limits: you can cap the job's CPU and memory with the usual systemd options.
- Dependencies: a job can start only after the network or a disk is available.
Quick comparison
| Aspect | cron | systemd timer |
|---|---|---|
| Ease of writing | One line | Two files |
| Jobs missed while the machine is off | Not recovered | Recoverable |
| Logs | Must be configured | Built into the journal |
| Overlapping runs | Possible | Avoided for the same service |
| Portability | Almost everywhere | Only systems with systemd |
Good practices for either tool
Robust scripts
Start shell scripts with set -euo pipefail, so they stop at the first error instead of carrying on with incomplete data. A backup script that continues after an error can overwrite a valid copy with an empty one.
Avoiding overlaps with cron
If you use cron for a job that can last longer than its interval, protect it with flock, which prevents a second copy from starting until the first has finished:
*/10 * * * * flock -n /tmp/sync.lock /usr/local/bin/sync.sh
Knowing when something doesn't start
Silence is the enemy of scheduled jobs: a backup that fails without warning is worse than no backup, because it gives a false sense of security. An effective technique is the reverse check: on successful completion, the script contacts a monitoring service. If the signal doesn't arrive by the expected time, the service sends an alert. This way you also discover jobs that never started at all, not just those that failed.
Watch out for time zones
Times refer to the server's time zone. With daylight saving time, a job scheduled at a time that is skipped or repeated during the change may not run or may run twice. Many administrators set servers to UTC to avoid surprises.
In short
- Cron is simple and universal; systemd timers offer catch-up of missed jobs, logging and protection from overlaps.
- In cron use absolute paths, redirect output and move the logic into scripts.
- With systemd,
Persistent=trueandjournalctlsolve two of the most common problems. - Write scripts that stop at the first error and avoid overlapping runs.
- Use a reverse check to know when a job fails or doesn't start at all.