systemd Timers guide
Timers are a built-in systemd mechanism for scheduling tasks.
Each timer is a systemd unit with a .timer
extension that triggers an associated .service
unit.
In practice, each *.timer
file must point to a *.service
file describing what should be executed.
systemd timers offer:
- calendar schedules (OnCalendar) and monotonic schedules (e.g., delays after system startup)
- event logging in
journald
- defining dependencies between services
If the server (or PC) was turned off at the time of invocation, the timer will make up for the service call after restart (with Persistent enabled), which in cron requires additional tools (cronrun
/anacron
)
Comparison with traditional cron
Cron is a classic task scheduling program. It's simple to use but limited. Timers offer greater flexibility and system integration:
Advanced time options
systemd timers can operate on a calendar basis (similar to cron) and monotonically - e.g., after a specified time from system startup or after another service ends.
This allows you to, for example, start a task a few minutes after system startup (OnBootSec=5min
) or cyclically repeat a task every 6 hours after it finishes (OnUnitActiveSec=6h
). Cron doesn't handle such dependencies or events.
Logging and monitoring
Each timer invocation (and the service it runs) is logged in journald.
You can easily check the history of calls and errors with a single command, e.g., journalctl -u custom.timer -u custom.service
.
In cron, results must be collected manually (e.g., by redirecting logs), and tasks from different users are scattered across separate crontabs. systemd allows you to list all timers with one command:
... giving you a complete overview of scheduled tasks (as opposed to searching through multiple crontab files).
Service management
Timers are systemd units, so you can assign resource control parameters, priorities, or systemcall filters to them. It might require adding dependencies - e.g., authentication or ensuring service B is running before performing a backup.
In the service or timer file, you can define Requires=
and After=
, guaranteeing the order of execution. Cron doesn't offer such control.
Systemcall filters in systemd (.service)
SystemCallFilter
filters in systemd restrict what a given process can execute at the Linux kernel level. They allow you to specify allowed or forbidden system calls (syscalls), such as open()
, mount()
, or execve()
.
This lets you:
- protect the system from logical errors or vulnerabilities in applications,
- secure a service against potential malicious activity,
- reduce the attack vector (hardening),
- enforce sandboxing for processes launched by
systemd
.
These filters are defined in the .service
file, e.g.:
A timer (.timer
) can run a .service
that has such safeguards - so scheduled tasks also operate in a limited, controlled environment.
Security and permissions
For system tasks, cron typically uses a single context (e.g., root or user), while systemd timers can function as system services or user-level services. You can also easily restrict access rights (e.g., using User=
in [Service]).
Characteristics of cron
Cron is simple and well-known, doesn't require systemd, so it also works on systems without it.
It also has simpler configuration ("one line" instead of two files).
It is less flexible:
- No support for events (only fixed calendar schedule)
- No easy monitoring mechanisms
- No task recovery mechanisms
- No resource control
Note
systemd timers are a more modern alternative to cron, especially when we need better control, logging, and flexibility. For simple, infrequent tasks, cron is still sufficient, but in many scenarios, systemd is more convenient.
Creating and configuring timers
To use a timer, you need two units: a .service
file (describing the task) and a .timer
file (describing the schedule).
How to create a timer:
1. Service file (.service
)
Defines the command to execute. Typical structure:
[Unit]
Description=Description
[Service]
ExecStart=/path/to/binary
Type=oneshot # (optional, e.g., for one-time scripts)
In ExecStart
, use full paths and make sure the program works correctly
2. Timer file (.timer
)
Specifies when the associated service should be launched. Basic sections:
[Unit]
Description=Task X timer
[Timer]
OnCalendar=... # calendar condition (see below)
# or:
OnBootSec=... # delay after system startup
OnUnitActiveSec=... # delay after the last service activation
Persistent=true # (optional: enables "catching up" at startup)
Unit=service_name # e.g., filename.service
[Install]
WantedBy=timers.target # so the timer activates at system startup
The timer file must have the Unit=
option set in [Timer]
, pointing to the .service
name.
We define the schedule time through OnCalendar (calendar expression) or monotonic OnBootSec, OnUnitActiveSec, etc.
As above, we can set Persistent=true
so systemd executes the task after restart if the server (or PC) was off during the scheduled runtime.
3. File placement
Files are typically placed in the /etc/systemd/system/
directory (for system tasks available to all users).
They can also go into /usr/lib/systemd/system/
or at the user level (~/.config/systemd/user/
).
After saving the files, it's worth checking their correctness:
If it doesn't return errors, the files are correct.
4. Starting the timer
After preparing the files, you need to load the timer into systemd and start it:
systemctl daemon-reload # reload systemd configuration (if we added files)
systemctl start custom.timer # starts the timer (doesn't start the service yet, but enters the schedule)
systemctl enable custom.timer # enables the timer at system startup
As usual, with enable
, an appropriate link to timers.target
will be created, so the timer will be automatically activated at each system startup.
5. Complete configuration example
Let's assume a script /usr/local/bin/important.sh
that we want to run 5 minutes after system startup, and then every 24 hours every morning (Mon-Fri at 10:00 AM). The unit files would look like this:
# /etc/systemd/system/important.service
[Unit]
Description=Important service
[Service]
ExecStart=/usr/local/bin/important.sh
Type=oneshot
# /etc/systemd/system/important.timer
[Unit]
Description=Timer of Important service
[Timer]
OnBootSec=5min
OnUnitActiveSec=24h
OnCalendar=Mon..Fri *-*-* 10:00:00
Unit=important.service
Persistent=true
[Install]
WantedBy=timers.target
The first will be called ~5 min after system startup, subsequent ones every 24 hours at 10:00 AM on weekdays. Thanks to Persistent=true
, if the computer was turned off at the scheduled time, the timer will run the service after the system starts.
Other applications
Practically any task from crontab can be transferred to a systemd timer.
An additional possibility is user timers (in ~/.config/systemd/user/
), which run tasks specific to a given account without the need for root privileges.
Debugging and monitoring timer operation
systemd provides tools for checking timer status:
Listing active timers
displays a list of all timers (with "NEXT" and "LAST" columns showing the next and last invocation), you can check when the timer is next scheduled to run.
Status of a specific timer or service
will show the current state (whether the timer is active, when the last call occurred, any errors during script execution).
System journal (journald)
All timer events and related service events are logged to journalctl
. To see logs for a given timer and service:
You can also use the shortcut -u custom.*
for both simultaneously. Useful flags:
-b
(only current system boot)-S today
(from today)-x
(detailed messages)-f
(real-time tracking)
Warning
If something isn't working, the first step is always to check journalctl
for error messages.
File verification
Before running, it's worth checking the syntax of the files:
systemd-analyze verify /etc/systemd/system/custom.timer
systemd-analyze verify /etc/systemd/system/custom.service
If there are errors (e.g., typos in option names), the tool will display appropriate warnings. For example, a typo like OnClendar
instead of OnCalendar
is usually ignored by systemd, so verify
helps catch such mistakes.
systemd-analyze calendar
For testing calendar expressions (OnCalendar
). E.g.
will show the exact times of the upcoming runs in a readable form. This helps configure complex schedules correctly.
Best practices and pitfalls
Some tips for working with timers:
Full paths and environment
In .service
files, always provide the full path to the script or program. The default PATH may be different than in cron.
If the script uses environment variables, set them explicitly (e.g., Environment=VAR=value
or in the .service
file).
Type=oneshot
mode
If the service performs only a one-time task (script execution), it's worth using Type=oneshot
in [Service]
, which properly handles termination.
Unit= and filename
Make sure the .timer
file has Unit=valid_custom.service
precisely defined. Not linking a timer to a service is a common cause of an inactive timer.
WantedBy=timers.target
In the [Install]
section, use WantedBy=timers.target
instead of multi-user.target
. This ensures the timer will be properly enabled when all system timers are started.
Check syntax
Even a small typo in an option name (OnCalendar
, etc.) is ignored without a critical error. Always use systemd-analyze verify
to catch such typos.
Monitor logs
If a timer isn't working, check the journal (journalctl
) and status. You'll often find information about error causes in the logs (e.g., permissions, missing file, etc.).
Use per-user timers (if needed)
systemd allows user-level timers (run with systemctl --user
). Useful for tasks that don't require root, e.g., file synchronization in Home.
Summary
systemd timers are comprehensive but require configuration discipline.
It's important to properly define .service
and .timer
files, use verification tools (systemd-analyze
, status
, journalctl
), and understand which options are responsible for scheduling.