Systemd timers are a scheduling mechanism built into systemd, an alternative to cron on distributions where systemd already manages services. Cron has not gone anywhere and keeps working, but timers offer capabilities cron lacks: logging through journalctl, explicit dependencies between tasks, retry on failure. Here is when switching to timers brings real convenience, and when it is simpler to leave cron as it is.
How a systemd timer differs from cron
Cron keeps the schedule in a single line of a text file and runs the command at the given time, with no knowledge of what happened before or after. A systemd timer consists of two units: the .timer sets the schedule, and the .service describes the task itself the same way as any other service — with dependencies, environment variables, and the user it runs as.
This separation means the same task can be started manually via systemctl, with its status and last result visible — without having to search for a record in root's mail or in a separate log the task itself had to set up on its own.
When a timer is more convenient than cron
The advantage of timers is most noticeable in a few specific situations:
- Visibility into run history and the exit code is needed without setting up separate logging — it is already in journalctl.
- A task must start only after another service has started successfully, for example after a database becomes ready.
- Retry on failure is needed: a timer can be set to restart the service after a given interval following an error.
- Several tasks compete for the same resources, and one must explicitly not start until another has finished.
- The schedule must survive a missed run due to the server being off — the Persistent option runs the task on the next boot.
How to create a timer and a service
The minimal set is two unit files with the same name and different extensions: .service with the actual command and .timer with the schedule, after which it is the timer that gets enabled, not the service directly.
# Enable and start the backup.timer timer
systemctl enable --now backup.timer
# Check the status and the time of the next run
systemctl list-timers backup.timer
Logs and the timer journal
The result of each timer run lands in journalctl under the .service name, rather than being mailed out as with cron. This is convenient to read at the moment, but the systemd journal has its own disk-size limit, which over time fills up with entries from all units, not only timers.
If a task itself writes a detailed log to a separate file — for example, the output of a backup or an import — that file, not the systemd journal, needs separate rotation, set up the same way as for any other growing log: this is covered in the article on configuring logrotate.
Application examples: PHP tasks and WordPress
Timers work equally well for custom PHP scripts run from the command line and for replacing WordPress's built-in WP-Cron with a system schedule — in both cases the task is described by an explicit command with a specific path to the php binary, rather than being triggered through the web server on every site visit.
The difference between running a PHP script from the CLI and from under FPM lies in the set of environment variables and the user permissions under which it runs, and this matters when moving a task into a service unit; details are in the article on PHP CLI and FPM in cron. A ready example of moving WP-Cron itself from running on visits to a system schedule is in the article on system cron for WP-Cron.
| Parameter | cron | systemd timer |
|---|---|---|
| Schedule storage | a line in crontab | a separate .timer unit |
| Run log | root mail or a custom log | journalctl with no extra setup |
| Dependencies between tasks | not supported | via unit parameters |
| Retry on failure | not supported | configured in the .service |
Frequently asked questions
Should old cron jobs be removed when switching to timers?
Yes, the same task should not be kept in crontab and in a timer unit at the same time — it will run twice and may cause a conflict, for example when writing to the same file simultaneously. Tasks should be migrated one at a time, checking each before removing it from cron.
Can timers be used inside a Docker container?
Inside an ordinary container, systemd as an init system is typically not running, so timers there do not work the same way as on a regular server. A scheduled task for a container is more sensibly run by a systemd timer on the host that reaches into the container from outside, rather than trying to run systemd inside the container itself.
What happens if the server was off at the scheduled run time?
By default such a run simply disappears, just like in cron. If the task must be guaranteed to run at least once per period, the Persistent option is enabled in the .timer unit — then the missed run executes right after the next system boot.