Whenever I want to use a cronjob on Linux, I eventually run into some issues. I’ve spent whole nights debugging why a cronjob didn’t run correctly. By default there doesn’t seem to be any logging support so anything sent to STDOUT or STDERR just disappear. There’s also hardly any sort of indication that the job started except by checking logs.
Instead of writing cronjobs though, you can now use Systemd timers instead. Systemd nicely logs everything so it’s accessible via
journalctl, and allows you to see the next scheduled execution time via
In this examle I’ve created a quick and dirty python script that just prints to stderr and stdout. If you want to use it, save the following script as executable at
#!/usr/bin/python3 import sys print("Hello from script.py") print("Writing to stdout") print("Writing to stderr", file=sys.stderr) print("Done!")
We first need to create the service systemd unit. As this won’t be a continuously running service, we use the
oneshot service type. Save the following as
[Unit] Description=A description of what your service does. [Service] Type=oneshot ExecStart=/usr/local/bin/script.py
Next we create the timer itself. It will be connected to the service by having the same filename, but with the different
.timer extension. Save the following as
[Unit] Description=Run script every day at noon [Timer] OnCalendar=*-*-* 12:00:00 Persistent=true [Install] WantedBy=timers.target
OnCalendar allows you to set timers to execute on a “calendar” or “realtime” basis. It uses a format of
DayOfWeek Year-Month-Day Hour:Minute:Second and like cron allows you to wildcard each component using
It’s also possible to set timers that execute on a “monotonic” basis, meaning they execute at a point relative to e.g. boot. Arch wiki has examples of both realtime and monotonic timers.
Next you need to enable the timer in systemd. You’ll need to run the following scripts.
systemctl daemon-reload # to reload available services systemctl start script.timer # to start the timer, note that this won't start the timer on next boot. systemctl enable script.timer # to automatically start the timer on boot (not the [Install section])
To test the timer, you can run the service using
systemctl start script
Check logs using
journalctl -u script.
root@mimikyu:/# journalctl -u script -- Logs begin at Sun 2019-09-08 00:23:42 CEST, end at Sun 2020-02-02 17:21:21 CET. -- Feb 02 17:21:17 mimikyu systemd: Starting A description of what your service does.... Feb 02 17:21:17 mimikyu script.py: Writing to stderr Feb 02 17:21:17 mimikyu script.py: Hello from script.py Feb 02 17:21:17 mimikyu script.py: Writing to stdout Feb 02 17:21:17 mimikyu script.py: Done! Feb 02 17:21:17 mimikyu systemd: Started A description of what your service does..
See a table of active timers on your system using
systemctl list-timers. Here’s what it looks like on my laptop.
root@mimikyu:/# systemctl list-timers NEXT LEFT LAST PASSED UNIT ACTIVATES Sun 2020-02-02 18:02:47 CET 38min left Sun 2020-02-02 17:03:00 CET 20min ago anacron.timer anacron.service Mon 2020-02-03 00:00:00 CET 6h left Mon 2020-01-27 06:33:47 CET 6 days ago fstrim.timer fstrim.service Mon 2020-02-03 01:37:32 CET 8h left Sat 2020-02-01 12:59:50 CET 1 day 4h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service Mon 2020-02-03 04:47:10 CET 11h left Sun 2020-02-02 11:28:40 CET 5h 55min ago apt-daily.timer apt-daily.service Mon 2020-02-03 06:33:12 CET 13h left Sun 2020-02-02 11:28:40 CET 5h 55min ago apt-daily-upgrade.timer apt-daily-upgrade.service Mon 2020-02-03 06:35:38 CET 13h left Sun 2020-02-02 16:58:23 CET 25min ago motd-news.timer motd-news.service n/a n/a Thu 2020-01-30 08:27:59 CET 3 days ago ureadahead-stop.timer ureadahead-stop.service 8 timers listed. Pass --all to see loaded but inactive timers, too.
From what I understand, this can also be done on a user-level basis. So the the service will execute with that user’s profile. It works similarily but you put the systemd unit files in different directories. E.g.
~/.config/systemd/user/. The arch wiki has a detailed page about it.