Cron jobs and systemd timers
Run tasks on a schedule: crontab basics, systemd timers and examples.
5 min read
On this page
For anything that should run regularly (backups, clean-ups, a script every hour) there are two ways: cron, the classic, and systemd timers, which can do more and write their output to the journal.
Covers Debian 12 and 13, Ubuntu 22.04 and 24.04, AlmaLinux 9 and Rocky Linux 9. All commands run as root unless stated otherwise.
cron
Installed?
command -v crontab || echo missingIf it is missing:
apt install -y cron && systemctl enable --now cron # Debian and Ubuntu
dnf install -y cronie && systemctl enable --now crond # AlmaLinux and Rocky LinuxEdit the crontab
crontab -eThis opens the current user's crontab in an editor. Each line is a task:
# minute hour day month weekday command
30 3 * * * /usr/local/bin/backup.sh| Field | Values |
|---|---|
| Minute | 0–59 |
| Hour | 0–23 |
| Day of month | 1–31 |
| Month | 1–12 |
| Weekday | 0–7 (0 and 7 are Sunday) |
* means "every", */15 "every 15", 1-5 "from 1 to 5", 1,15 "on the 1st and 15th".
Examples
# Every day at 3:30 am
30 3 * * * /usr/local/bin/backup.sh
# Every 15 minutes
*/15 * * * * /usr/local/bin/check.sh
# Monday to Friday at 8 am
0 8 * * 1-5 /usr/local/bin/report.sh
# On the 1st of every month at midnight
0 0 1 * * /usr/local/bin/monthly.sh
# After every reboot
@reboot /usr/local/bin/on-start.sh
# WordPress cron every 5 minutes as the web server user (Debian/Ubuntu)
*/5 * * * * sudo -u www-data php /var/www/example.com/wp-cron.php > /dev/null 2>&1To create a task for another user, use crontab -u www-data -e. Then you do not need sudo -u in the line.
Things to watch out for
- cron has almost no environment variables.
PATHis very short. Write commands with full paths (/usr/bin/php,/usr/local/bin/…), or putPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binat the top of the crontab. - Output otherwise goes nowhere. cron wants to email it; without a mail server it is lost. Redirect it to a file:
… >> /var/log/myjob.log 2>&1. %has a special meaning in the crontab (newline). In commands likedate +%F, write it as\%, or put the command in a script.- Time zone. cron uses the server's time zone.
timedatectlshows it,timedatectl set-timezone Europe/Berlinchanges it.
Check that it worked
crontab -llists your tasks. To see whether a run happened:
journalctl -u cron --since today # Debian and Ubuntu
journalctl -u crond --since today # AlmaLinux and Rocky Linuxsystemd timers
A timer consists of two files: a service that says what runs, and a timer that says when. Advantages over cron: output goes to the journal, missed runs can be caught up, and two runs never overlap.
Example: clean-up script every night
/etc/systemd/system/cleanup.service:
[Unit]
Description=Delete old files in /var/tmp/uploads
[Service]
Type=oneshot
ExecStart=/usr/bin/find /var/tmp/uploads -type f -mtime +7 -delete/etc/systemd/system/cleanup.timer:
[Unit]
Description=Clean up every night
[Timer]
OnCalendar=*-*-* 04:00:00
Persistent=true
[Install]
WantedBy=timers.targetEnable it:
systemctl daemon-reload
systemctl enable --now cleanup.timerIf the service should run as another user, add User=www-data under [Service].
Time expressions for OnCalendar
| Expression | Meaning |
|---|---|
hourly | every full hour |
daily | every day at midnight |
weekly | Mondays at midnight |
*-*-* 03:30:00 | every day at 3:30 am |
Mon..Fri 08:00 | weekdays at 8 am |
*:0/15 | every 15 minutes |
*-*-01 00:00:00 | on the 1st of every month |
To check an expression and see when it fires next:
systemd-analyze calendar "Mon..Fri 08:00"Instead of a time you can also use an interval, e.g. OnBootSec=5min and OnUnitActiveSec=1h for "5 minutes after boot, then hourly".
Check that it worked
systemctl list-timersshows all timers with their last and next run. Output and errors of the last run:
journalctl -u cleanup.service -n 50 --no-pagerRun it once by hand right now, without waiting for the timer:
systemctl start cleanup.serviceCommon errors
- The command works in the terminal but not in cron. Almost always
PATHor a relative path. Use full paths andcdinto the right folder inside the script. - The cron script does not run at all. Is it executable?
chmod +x /usr/local/bin/backup.sh. Does it start with a line like#!/bin/bash? - The timer is missing from
list-timers. You forgotsystemctl daemon-reloador did not enable the timer withenable --now. Failed to start … Unit … not found. Service and timer must have the same name (cleanup.serviceandcleanup.timer), otherwise the timer needs aUnit=line under[Timer].- The task runs at the wrong time. Check the server's time zone (
timedatectl).
What next
- A daily backup as a timer: Set up backups.
If you get stuck, open a ticket.