Key Takeaways
- systemd timers have internal checks to prevent multiple instances from running simultaneously, avoiding resource conflicts.
- Enable one-second resolution in systemd timers to increase accuracy compared to the default one-minute resolution.
- Have failures reported to you via the medium of your choice. Launch timers and services immediately to aid in debugging.
Linux’s systemd timers boast a rich and sophisticated set of features. The basic features handle most common scenarios, but you might be missing out on some cool functionality like these little-known options.
The systemd Timer
The systemd timer is the modern replacement for the older cron command. Both commands allow you to schedule a task or process to be launched at a certain time, or with a certain frequency. The advantages of systemd timers are more control, more flexibility, and higher resolution than cron timers.
Based on the comments and questions I get when I talk about them, systemd timers might be one of the most misunderstood elements of Linux. Even when I’m speaking to people who prefer systemd timers, they often don’t know all the things on this list.
At first glance, systemd timers can seem challenging. There’s a new configuration syntax to learn, which many find off-putting. And you don’t just create a single configuration file. Every timer needs two separate, but related, files.
On first impression, systemd timers may appear complicated, or only for techies. But it’s really not that bad. It’s a shame some people avoid them, and stick with cron, because systemd timers have a lot of neat tricks up their well-stocked sleeves.
1. One Instance at a Time
The first benefit of systemd timers comes automatically. You don’t need to do anything to enjoy it. There are internal checks that prevent a timer from being triggered if a previous run of the same timer hasn’t yet completed.
This prevents resource conflicts, and avoids the type of problems that can arise if, say, two backup jobs are trying to run at the same time.
Timers that don’t run are not discarded. They’re held in readiness, and launched when the running timer has completed. This is important because sequential launching of the timers is perfectly fine. It’s unplanned concurrent execution that can yield unexpected side effects.
2. Turn on Second-Resolution Timing
With cron, you can schedule tasks with a resolution of one minute. Now, with system timers, you can schedule timers to trigger with a one-second resolution. Actually, you can use a microsecond resolution but, outside of specialist real-time scenarios, it’s difficult to imagine when you’d need to use that.
But here’s the thing. systemd timers default to a one-minute resolution. You need to turn on the accuracy of one-second resolution like this.
[Timer]
AccuracySec=1
3. Use Monotonic Events for Delayed Execution
systemd timers have an extensive and flexible set of scheduling methods.
Calendar events are triggered at a particular time on a particular day or set of days, and at a specific time. You can have more than one time specifier for a timer, to give you different launch times on weekends and weekdays. There’s also scope to have no time specified, which effectively means “anytime during the day.”
Monotonic events are triggered after a specified duration following an event, such as boot-up of the system. You can also configure a timer to trigger at some point after it was activated or, surprisingly, deactivated.
To have a service run 45 seconds after the PC has booted, use this format:
[Timer]
OnBootSec=45 seconds
Another neat trick is to have the timer triggered if the time or date is changed in the system clock of the PC.
[Timer]
OnClockChange=true
4. Manually Trigger Timed Tasks
When I’m developing a timer, I like to do so in stages. First, make sure the target process you’re going to launch works as a standalone process. That means, the program, script, or Linux command you intend to launch, should work as expected when you use the command in a terminal window.
Second, I create the service file, and put that command into it. Finally, I create the timer file that launches the service file.
To test your timer, you can set it to trigger a few minutes into the future, and wait. This works, but if you have to do this several times to get things working, adjusting the OnCalendar= line each time you make a change soon becomes tedious.
You can launch your timer manually whenever you want it to run.
sudo systemctl start name-of-your.timer
This works, but you need to keep in mind that this launches your timer. But, if your OnCalendar= line instructs the timer to launch the service on the first Wednesday of the month, you’ll need to wait for that to roll round before the service is triggered.
If what you really want to test is the execution of the service, start the service instead.
sudo systemctl start name-of-your.service
There’s a corresponding stop command, too.
sudo systemctl stop name-of-your.service
Note that if the RefuseManualStart or RefuseManualStop units are included in your service file, and set to Yes, you’ll be unable to manually start or stop the service. But, if you’re debugging a systemd timer that you’ve created yourself, you probably haven’t included these. If you need to, you can comment them out while you’re testing.
5. Automatically Report on Failing Services
The MAILTO directive was one of cron’s slicker features. You provided an email address to receive notifications of errors or failures. The systemd equivalent is slightly more convoluted, but it does offer more flexibility. The notification method need not be email. You could just as easily send a notification to a Slack channel, for example.
The trick is to trigger another service and have that service launch the process that sends the notification.
In your service file, include the OnFailure= directive and set it to the name of your error reporting service. The name of your notification service must end with an at sign “@” to indicate it is a template service.
[Unit]
OnFailure=notify-error@%n.service
Our reporting service is called “notify-error@.service.” If there is a failure and this service is called, the name of the calling service is appended after the “@”, replacing the “%n” token. That name can be retrieved in the notify-error@.service as the “%i” token.
Our notify-error@.service contains:
[Unit]
Description=Report systemd timer errors[Service]
Type=oneshot
ExecStart=/usr/local/bin/send-to-slack.sh %i
[Install]
WantedBy=multi-user.target
When this service is triggered, the name of the calling service can be accessed via the “%i” variable. On my machine, this is passed to a script file called “send-to-slack.sh” that composes the message, and sends it to Slack. The ExecStart= line could point to any command, program, or script that you have on your computer.
And Finally
A final tip is to use the systemd-analyze tool with its calendar option to verify and normalize dates and times for use in OnCalendar statements in your timer file.
For example, to have a timer triggered at 13:15 every day, provide the time on the command in a simple format, and have systemd-analyze generate the normalized version for you.
systemd-analyze calendar 13:15
The normalized format is the one to use in your timer file.