Scheduling: cron & systemd timers
crontab syntax, environment gotchas, logging cron jobs, and systemd timers as a modern alternative.
Backups at 2 a.m., a cleanup every hour, a report every Monday morning: servers are full of jobs that must run on a schedule without anyone logged in. Linux has two tools for this: the classic cron, and systemd timers. You'll learn both, along with the gotchas that make cron jobs mysteriously fail.
cron basics#
The cron daemon wakes up every minute, checks the schedules, and runs whatever is due. Each user has a personal crontab (cron table):
On Ubuntu and Debian, cron is installed and running by default. On Fedora/RHEL it's called cronie: sudo dnf install cronie && sudo systemctl enable --now crond.
The crontab format#
Each line is five time fields followed by the command:
Common schedules:
💡 Unsure about a schedule? Paste it into crontab.guru for a plain-English explanation.
When both day-of-month and day-of-week are set (not *), cron runs the job when either matches. 0 0 13 * 5 means "the 13th AND every Friday", not "Friday the 13th".
A real crontab#
Things to notice:
- Absolute paths everywhere.
- Output is redirected to a log file (
>> file 2>&1) or sent to syslog withlogger. flock -nstops a new run from starting while the previous one is still going (very important for frequent jobs).MAILTO=""turns off cron's attempts to email output. SetMAILTO=you@example.comif the server can send mail.
Why cron jobs fail (and how to fix them)#
1. A minimal environment. Cron doesn't read your .bashrc. PATH is usually just /usr/bin:/bin, there are no aliases, and none of your exported variables exist. Use absolute paths, set PATH in the crontab, or set what you need at the top of the script. To see exactly what cron gives you, add a temporary job:
2. The working directory is $HOME. Relative paths like ./data resolve from your home directory, not from where the script lives. Inside scripts, cd explicitly. This idiom changes into the script's own directory:
3. % characters. In a crontab line % means newline, so date +%F breaks. Escape it as \%, or (better) put the command in a script:
4. No output, so no clue. Always log. Then check the log, the journal (journalctl -u cron, or -u crond on Fedora), or grep CRON /var/log/syslog to see if the job started at all.
5. Permissions and the shebang. The script must be executable (chmod +x) with a correct #! line, or call it as /bin/bash /path/script.sh.
6. The timezone. Cron uses the system timezone (timedatectl). Servers are often on UTC, so 0 9 * * * may not be 9 a.m. where you are.
Testing a job like cron would
Run your script with an empty environment to catch PATH and variable problems before cron does:
(Here bash supplies a default PATH because the environment was empty; cron sets its own, similar minimal PATH.)
System-wide cron#
Besides personal crontabs, there are system locations (edit with sudo):
Access control: /etc/cron.allow and /etc/cron.deny decide which users may have crontabs.
systemd timers: the modern alternative#
On systemd systems, a timer unit can trigger a service unit on a schedule. Compared with cron you get logs in the journal automatically, no overlapping runs (a service that's already running isn't started again), dependency handling, resource limits, randomised delays, and catch-up of runs missed while the machine was off (Persistent=true).
Two files, a service and a timer with the same name:
Enable the timer (not the service):
OnCalendar examples: hourly, daily, weekly, Mon..Fri 08:15, *-*-01 00:00:00 (monthly), *:0/15 (every 15 minutes). Check an expression with systemd-analyze calendar:
There are relative timers too: OnBootSec=5min (5 minutes after boot), OnUnitActiveSec=1h (an hour after the last run).
cron or timer?
One-off jobs: at#
To run something once later, use at (sudo apt install at):
For a one-off with systemd: systemd-run --on-active=30m /home/ada/bin/task.sh.
Common mistakes#
- Relying on your interactive environment (PATH, aliases, variables) inside cron jobs.
- Not logging output, so failures are invisible.
- Unescaped
%in crontab lines. - Overlapping runs of a slow job scheduled every few minutes. Use
flockor a systemd timer. - Forgetting the timezone: servers are often on UTC.
crontab -rinstead ofcrontab -e. The keys are next to each other, and-rdeletes everything without asking. Keep a copy of important crontabs (crontab -l > crontab.bak).- Enabling the
.serviceinstead of the.timerwith systemd.
What's next#
Many scheduled jobs talk to other machines. Next, start the final module with networking commands: IP addresses, ports, DNS, and how to troubleshoot connectivity.
Check your understanding
Quick quiz
1.What schedule does
30 2 * * 1-5mean?2.A script works when you run it by hand but fails from cron with 'command not found'. The most likely cause?
3.In a crontab line, why must
date +%Fbe written asdate +\%F?
Finished reading?
Mark this lesson complete to track your progress.