Skip to content
CitexNode
All guides

Cron jobs and systemd timers

Run tasks on a schedule: crontab basics, systemd timers and examples.

5 min read

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?

bash
command -v crontab || echo missing

If it is missing:

bash
apt install -y cron && systemctl enable --now cron       # Debian and Ubuntu
dnf install -y cronie && systemctl enable --now crond    # AlmaLinux and Rocky Linux

Edit the crontab

bash
crontab -e

This opens the current user's crontab in an editor. Each line is a task:

text
# minute  hour  day  month  weekday  command
  30      3     *    *      *        /usr/local/bin/backup.sh
FieldValues
Minute0–59
Hour0–23
Day of month1–31
Month1–12
Weekday0–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

text
# 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>&1

To 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. PATH is very short. Write commands with full paths (/usr/bin/php, /usr/local/bin/…), or put PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin at 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 like date +%F, write it as \%, or put the command in a script.
  • Time zone. cron uses the server's time zone. timedatectl shows it, timedatectl set-timezone Europe/Berlin changes it.

Check that it worked

bash
crontab -l

lists your tasks. To see whether a run happened:

bash
journalctl -u cron --since today     # Debian and Ubuntu
journalctl -u crond --since today    # AlmaLinux and Rocky Linux

systemd 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:

ini
[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:

ini
[Unit]
Description=Clean up every night

[Timer]
OnCalendar=*-*-* 04:00:00
Persistent=true

[Install]
WantedBy=timers.target

Enable it:

bash
systemctl daemon-reload
systemctl enable --now cleanup.timer

If the service should run as another user, add User=www-data under [Service].

Time expressions for OnCalendar

ExpressionMeaning
hourlyevery full hour
dailyevery day at midnight
weeklyMondays at midnight
*-*-* 03:30:00every day at 3:30 am
Mon..Fri 08:00weekdays at 8 am
*:0/15every 15 minutes
*-*-01 00:00:00on the 1st of every month

To check an expression and see when it fires next:

bash
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

bash
systemctl list-timers

shows all timers with their last and next run. Output and errors of the last run:

bash
journalctl -u cleanup.service -n 50 --no-pager

Run it once by hand right now, without waiting for the timer:

bash
systemctl start cleanup.service

Common errors

  • The command works in the terminal but not in cron. Almost always PATH or a relative path. Use full paths and cd into 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 forgot systemctl daemon-reload or did not enable the timer with enable --now.
  • Failed to start … Unit … not found. Service and timer must have the same name (cleanup.service and cleanup.timer), otherwise the timer needs a Unit= line under [Timer].
  • The task runs at the wrong time. Check the server's time zone (timedatectl).

What next

If you get stuck, open a ticket.