Systemd is a powerful and complex init system for Linux servers, and some of its features can be a bit challenging to learn. In this video, Jay goes over systemd timers, a feature of systemd that’s often overlooked. You’ll learn how systemd timers are different from cron jobs, how to set them up, and more!
Thanks to Akamai for sponsoring today’s video! Check out Akamai’s Connected Cloud to launch your very own Linux server, with $60 in free starter credit!
Systemd timers vs cron Jobs
The first thing you may be wondering, is what systemd timers are in the first place, and/or you’re wondering what makes systemd timers better than cron jobs (or why you should learn this in the first place).
First of all, for those that aren’t already aware, cron jobs give you the capability of setting up a task to automatically run according to some schedule. Maybe you want to run a script at midnight every Sunday, or schedule a process check every 15 minutes. (I went over cron in detail within its own dedicated video, so I won’t go into any more detail here). If you haven’t seen that video yet, it would be a great concept to learn.
When it comes to what makes systemd timers better than cron Jobs, the thing is, they’re not. The point of this lesson is to educate you on what systemd timers are and how you go about using them, so that way you can decide for yourself which to use. It’s actually a great idea to learn both. The best Linux Administrators out there have a varied toolset, and both of these should definitely be part of your toolset!
As far as what’s different about systemd timers compared to cron jobs, let’s go over that right now.
- Systemd timers are part of systemd, while cron is more of a stand-alone thing. You can use cron jobs on systemd systems, you can use cron jobs on non-systemd systems, but you can’t use systemd timers on non-systemd systems.
- One benefit of systemd timers is that there’s additional features when it comes to scheduling things, that let you fine tune when they’ll be re-attempted when missed, process priority, how likely a job run via a timer is to be killed via the out of memory killer, and more.
- However, with that added flexibility comes added complexity. cron jobs end up being much simpler to understand and implement, while systemd timers end up being a bit more involved. That’s not to say that systemd timers are hard to learn and understand, that’s not the case at all. There’s just more to learn in regard to systemd timers when compared to cron jobs.
- You can check on the status of systemd timers with systemd-related commands you may already know, which makes it much faster to learn if you already have knowledge of that. And this is also why I recommend you watch my systemd video first, although again, that’s not required.
Of course, there’s more that sets systemd timers apart from cron jobs than that, but you’ll automatically start to understand the differences more as we work through the examples.
When should you use Systemd Timers?
When should you use cron jobs, and when should you use systemd timers? Well, my honest answer is to use whichever one fits the needs of your project or fits in better within your environment. But to help you decide, consider these tips:
- Regardless of which you use, the resulting task that’s executed won’t run any faster on one when compared to the other.
- If you use a distribution that doesn’t feature systemd as its init system, the choice is made for you. Systemd timers require systemd.
- If you’re pressed for time or otherwise wanting to schedule something simple to run on a schedule, cron jobs are almost always faster and simpler.
- On the other hand, if you want to get more technical, such as specifying missed jobs should be run the next time the server boots, setting up a job to bypass the out of memory killer, or scheduling a job with more granular control than just simply a day and time – systemd timers will be your best bet.
- If you want to add automation to a solution that interacts with systemd as part of its functionality, then systemd timers are the way forward.
In summary, systemd timers integrate better within your environment if you already use systemd, and offer you additional features that cron itself doesn’t offer. On the other hand, cron jobs are easier to understand and simpler to implement. Which one you end up using will probably come down to the complexity of the task at hand and which of the two solutions are a better fit for your project on a case by case basis.
Systemd Timers in Action
It’s time to see systemd timers in action, and work through some hands-on examples to help you learn it. We’ll begin with a very simple example.
Let’s say that we have a web server, and we want to send the message of the day to our users on a regular schedule. What we’ll do, is look at how to achieve this with a systemd timer. The first thing we have to test, is that the command we want to schedule works by itself. For this example, we can use the wall command to send a message to users:
wall "Hello World"
The wall
command is fairly simple, you type the wall command along with a message in quotes, and other users logged into the server will see it.
To make this more fun though, let’s change our wall message to be a bit more useful:
wall $(curl wttr.in?format=3)
How cool was that?
Now, before I explain this I just want to say, don’t worry about memorizing any of the commands I’ve shown you so far, these are just examples of something we can automate. If you memorize nothing about the wall
command and just copy and paste everything up to this point, that’s totally fine.
For those of you that are curious, the command we just ran executes the wall
command inside of a subshell, and when paired with the wall command, it’ll send everyone on the system a weather report. Whether they wanted it or not.
But what does this have to do with systemd timers? Well, what we’re going to do is create a systemd timer that will run the wall
command we just ran, automatically on a specific schedule.
However, in order to create a systemd timer, we have to have something for it to execute when it triggers. Systemd timers can execute systemd services, for example, so we can create a systemd service to give the timer something to do.
So what we’ll do is create a new systemd service file:
sudo nano /etc/systemd/system/weather-report.service
Inside that file, we’ll place the following code to create a simple service file to execute that wall
command:
[Unit]
Description=Weather Report Service
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/bin/sh -c 'wall $(curl wttr.in?format=3)'
[Install]
WantedBy=default.target
We’ll save the file, and then we’ll refresh systemd so that way it’s aware of the fact we added a new service file:
sudo systemctl daemon-reload
Now, when we want to send a weather report to every user on the system, all we need to do is start the systemd service for it:
systemctl start weather-report.service
Let’s take a closer look at how this file works.
What we have here is a systemd unit file, specifically, a service file. As I discussed in my systemd video, systemd works with unit files, and a service is a type of systemd Unit file.
An even simpler explanation is that a service file, like this one, contains a set of instructions that tells systemd how to execute something.
Keep in mind, we haven’t even covered systemd timers yet, we’re creating this service file because having one is a prerequisite for a systemd timer. Think of it this way, we have to give the timer a job to do, and this service file will be the job we’ll ask it to perform for us.
I’m not going to go over the structure of this systemd file in detail here, since I went over that in the systemd video. But one thing I do want to refresh you on is the ExecStart
line. This is what will end up getting executed when this service file triggers. You’ll also notice that I’ve structured the command differently within this service file, when compared to how I ran it on the command line.
The reason I typed it a bit differently here is because when a systemd service triggers, it doesn’t contain a full shell environment, and we’ll get some strange errors if we paste the command here, that we ran earlier. Now this is only necessary because of the subshell, you won’t have to type commands here any differently than you normally would, unless a subshell is involved. If I wasn’t using a subshell, I would simplify the command portion down to only what’s inside the quotes.
Anyway, now that we’ve created a service file, and we’ve also verified that it works, it’s time for us to look at how we can schedule it to run automatically with a systemd timer.
So, what we’ll do, is create a new systemd unit file, and the type of unit file we’ll create is a timer:
sudo nano /etc/systemd/system/weather-report.service
At this point, we have an empty and unsaved file open in our text editor. And what we’ll do, is go over an example of a timer that we’ll paste into this window.
But before we do, there’s a few important things I’d like you to notice and keep in mind.
First, I mentioned that systemd works with unit files. We created a Unit file in the previous example, specifically a service file, which is a type of unit file. Now, we’re creating the second unit file of our lesson today, but this time the type of unit file we’re creating is a timer.
The reason I’m pointing this out now is because the relationship between these files is confusing for anyone starting out. So I want to make sure that the relationship is clear to avoid you getting confused later. Again, systemd works with units, which are simple text files that configure how systemd accomplishes something. Services and timers are two examples of systemd units.
The next thing I want to point out, is that the path and filename of the timer file we’re creating right now is extremely similar to the path and filename of the service file we created earlier. This is absolutely not a coincidence. When you have a service file with the same name as the timer file, with the only difference being the file extension, then Systemd automatically considers them to be related. This will make more sense in a moment.
So, here’s our systemd timer code:
[Unit]
Description=Weather Report Service
[Timer]
OnCalendar=Mon..Fri 10:00
[Install]
WantedBy=timers.target
Let’s go over this file. First, we have a description, nothing unusual there. Under the timer section, we have OnCalendar
here, and we’re giving it a range starting with Monday and ending with Friday. Also, we’re setting it for 10:00am, so as you can probably guess this means that the timer will trigger every weekday at 10:00am.
For the last line, we’re not going to go into much detail about that here because it goes beyond the scope of this video, deeper into the innards of systemd than will be useful to us today. A short summary is that targets are another type of unit file, and when you link them like this, the current file will inherit specific characteristics from the target. But again, any further than that and this video starts to get a lot longer. One thing at a time.
At this point though, it may look like we’re missing something. We’re not, but if you’re just starting out, you might be wondering how we go about instructing this timer unit which service file we want it to start when it triggers. But that’s the interesting part – you don’t. The timer will look for a file name that’s the same as its own, but with a .service
extension instead of a .timer
extension. So therefore we don’t tell the timer which service file to start, it already knows what its looking for. All we have to do is match the naming scheme.
Anyway, to set this up, we’ll save the file, and then exit out of the editor.
Next, we’ll refresh systemd again:
sudo systemctl daemon-reload
At this point, we’ve created two units, a service unit and a timer unit. We’ve also refreshed systemd so that it’s aware of the timer we just created. But how do we go about enabling the timer? Should we enable the timer? What about the service? should we enable that?
Those are questions you might be asking if you’re already somewhat familiar with systemd. When we enable a service file for example, we’re instructing systemd to start that unit file when the server starts. We do that by typing systemctl
, enable
, and then the name of the unit.
Okay, so do we need to enable the service file we just created? Actually, no, we don’t. We can, if we want to, but it’s not required that we enable a service when a timer is present. If you do enable the service, that means the service will start when the server boots up. If you disable the service file, then it won’t start when the server boots. So the behavior of the service file itself doesn’t change at all, in any way shape or form, when a timer is present.
With a timer present, it will cause the service file with a matching name and a file extension of .service
to start. What the trigger will do is literally just start the service, and the result is exactly the same as if you typed systemctl start weather-report.service
, like we did earlier. That’s what the timer does, but how do we enable it?
It’s simple really – if you enable a systemd timer, then the timer will be active and waiting for it’s scheduled time to trigger as soon as the server boots.
But what happens when you start a timer? Does it start the countdown, or does it start the associated service file? Well, what it does, is start the timer itself, which itself starts a countdown, so the answer is that the timer will be active and waiting for its trigger time as soon as you start it. If you never start it, and it’s not enabled either, then nothing happens as far as the timer is concerned until you do.
Anyway, let’s see it in action. But we may want to make a slight alteration:
sudo nano /etc/systemd/system/weather-report.timer
The change that we’re going to make is with the OnCalendar
section. Unless it just so happens to be getting close to the sample time, let’s change the time such that it will trigger about 3 minutes or so in the future so we can see it trigger much sooner.
So we can save and close the file, and then refresh systemd to be safe:
sudo systemctl daemon-reload
Next, we’ll start our systemd timer, with the logic being that enabling it won’t do us much good unless we want to reboot the server first, so we’ll simply start the timer now:
sudo systemctl start weather-report.timer
If you did want this timer to be active every time the server boots, then we can enable it if that’s what you want:
sudo systemctl enable weather-report.timer
Anyway, all we have to do at this point is set back and wait, and once the trigger time happens, we’ll see a weather report inside our terminal.
Adding Persistence to our Timer
One problem with our timer currently is that if the server is down for any reason, then the timer won’t trigger. That’s pretty obvious, a server can’t perform a job if it’s not even running. But what if we wanted the weather report to be sent at the next available opportunity once the server starts up? Let’s add a new option to the timer:
[Unit]
Description=Weather Report Service
[Timer]
OnCalendar=Mon..Fri 10:00
Persistent=true
[Install]
WantedBy=timers.target
So what we’ve done is we’ve added and enabled the Persistent
option, which does exactly what we want it to. If the OnCalendar
time is missed, the task will be run once the server starts up next.
Let’s not stop there, though. We can add another option:
OnBootSec=15m
And what that will do for us, is ensure that the server has been running for at least 15 minutes before the timer will trigger. We might add something like this to a server to prevent a job from running until well after the server has started all other services and performed any other missed jobs, the delay we’re adding here will ensure that this timer in particular has a delay attached to it, so it doesn’t get in the way of any other startup jobs.
There’s definitely other options we can add here, but for now I’d like to break from that and look at the syntax for setting the OnCalendar
trigger time. I mean, what we have here is easy to understand but we might not’ve been able to come up with that ourselves if we didn’t already have that in the example.
Now, the topic of time in systemd is a very large one, so what I’m going to do is stick to what I think is the absolute simplest method that I’ve found to figure out what to add to the OnCalendar
line.
First, let’s start with a timestamp, like this one:
Monday 2032-08-16 10:08:00
This is a completely random date and time that I made up. Now we could add this timestamp to the OnCalendar
line as-is, but if we do, the timer will execute specifically on that single day. And that might be fine if that’s what you want, but it probably isn’t. Let’s change it a bit.
* 2032-08-16 10:08:00
That changes it, a bit, but not by much. If we set it to this, the timer would still trigger the exact same date and time, it’s just that it doesn’t matter what day of the week that date and time happens to be. And that’s the only benefit we get from this, we don’t have to know the day of the week. Let’s adjust it a bit more.
* *-08-16 10:08:00
Now we’re getting somewhere! If our timer is set to this, then it will trigger every year at this date and time. This is when we start to wander into the territory of recurrence, because now we have an asterisk for the year which means it doesn’t matter what the year is, so long as the rest of the line is true.
From there, we can make it run even more frequently, such as changing it to monthly:
* *-*-16 10:08:00
So now on the 16h of every month at 10:08:00, the timer will trigger.
* *-*-* 10:08:00
Now, the trigger will happen daily since before we had the 16th listed in this field, now we have an asterisk, which means anything. Since it no longer matters what day of the month it is, this task will now trigger on each day of the month.
Next, here’s a pop quiz – when will a task with the following timestamp trigger?
* *-*-* *:*:00
If you answered “every minute”, then you’re correct! Since we left the seconds at :00, every time the second field hits 00 it will trigger, and of course, that works out to be every minute.
So, the simple cheat that I’ll give you is this. When you want to come up with a time stamp for your calendar, write down the desired day, date, and time you’d like the trigger to execute on, and follow this format:
Monday 2032-08-16 10:08:00
After that, for any item that doesn’t matter when it comes to the timestamp you’d like the task to run on, change that field to an asterisk. Repeat that until all you have remaining other than asterisks are specific numbers that match your desired recurrence.
Once you’ve converted it, copy and paste it into the OnCalendar
field. Done!
Now that we’ve made that change, perform another daemon-reload like we’ve done earlier. After that, start and enable the timer. You can run systemctl enable --now nameoftimer.timer
to enable and start the timer with one single command.
After that, check the status of your timer:
systemctl status nameoftimer.timer
Not only will this inform you if you’ve made errors, it will also tell you when the timer will trigger next. That way, you can not only get a reminder on when the timer will trigger, you’ll also be able to verify that it plans on triggering exactly when you want it to. Then, you can rest assured that you’ve configured the timer correctly.
But how do you check the status of a service, AFTER the timer was supposed to trigger? Well, that’s easy. You check the status of the service file, since that’s what the timer will start when it triggers. Basically, the same as you would any other service file.
I want to be clear though, this isn’t the only way to prepare the OnCalendar
line, it accepts other formats too, but my goal with this video is to help you understand how to use this on a day-to-day basis, and this is the easiest and straight-forward method that I’ve found.
Again, here’s how the process of creating a timer in Systemd breaks down:
- Create a Systemd Service Unit (afterall, we have to have something to run when our timer triggers)
- Create a Systemd Timer Unit, with the same name and path as the service, but with an extension of .timer instead
- To calculate a time stamp, follow the format: DayOfTheWeek YYYY-MM-DD HH:MM:SS. Change every field to either an asterisk or a specific value to narrow it down to when you’d like it to trigger. Save the file when you’re done
- If you want the timer to start counting down as soon as the server boots, enable the Timer with
systemctl
- If you want the timer to start counting down now, stop the timer with
systemctl
- You can also run systemctl disable to turn the timer off
- If you want to would also like the service file to run every boot, completely separate from the timer, enable the service as usual. Enabling both the service and the timer doesn’t contradict anything, the service being enabled will make it run at boot, the timer being enabled will make it start counting down at boot. Both can coexist separately and also the timer can trigger the service whenever it needs to in parallel.
There’s one more example I’d like to give you guys before I close out this lesson. This example is going to forego the use of OnCalendar
completely when it comes to the timer file. Also, this next example is going to be generic since we don’t need to follow the weather report scheme anymore. Let’s take a look:
[Unit]
Description=Timer description
[Timer]
# Execute job even if it was missed
Persistent=true
# Wait for 15 minutes before running the task for the first time
OnBootSec=15m
# After that, repeat the task every 5 minutes
OnUnitActiveSec=5m
[Install]
WantedBy=timers.target
Here, I’ve added some comments to help you as well. But the interesting part here is that like I mentioned, the timestamp is omitted completely. But, this will still work actually. Here’s how this timer file breaks down.
First, we type a description, no surprise there. Next, as we went over earlier, Persistent
being enabled means that this timer will trigger if it missed it’s most recent trigger time, so that way you can make sure a job runs no matter what.
Continuing, we have OnBootSec
, which is optional, and in this case I have it set to 15 minutes so that the timer won’t trigger until the system has been running for least this long.
So far, everything I’ve mentioned isn’t really new. But the OnUnitActiveSec
option is definitely new. What this will do, as the comment above it suggests, is trigger our timer every 5 minutes. So putting this all together, this timer will wait until the server has been running for at least 15 minutes, and then it will run once for every five minutes the unit has been active. So the timer started running at 10:12, then it will trigger our timer at 10:17am and again every five minutes thereafter.
And that’s all I planned on going over in today’s video. Sure, there’s a lot more I could cover when it comes to systemd timers, especially the additional configuration lines you can add to a timer, but we can’t cover everything in one video when it comes to something like this.
Instead, I decided to focus on what’s required for you to know in order to start using systemd timers in your day-to-day Linux administration routine, and hopefully you found it helpful…