Systemd timers are the modern alternative to cron on Linux. Each job is a normal systemd service, so you get logging in journalctl, resource limits, dependencies and the ability to catch up on runs missed while the server was off. This guide converts a typical cron job into a timer.
Before you start
- Root access to any systemd-based distro (Ubuntu, Debian, AlmaLinux, Rocky).
- A script that works when run by hand, e.g.
/usr/local/bin/backup.sh(executable, with a shebang line).
How timers work
You create two unit files with the same name:
backup.service: what to run.backup.timer: when to run it.
You enable the timer, not the service; the timer starts the service at the scheduled times.
Create the service
Create /etc/systemd/system/backup.service:
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=root
Nice=10
IOSchedulingClass=idleType=oneshot suits scripts that run and exit. Nice and IOSchedulingClass keep the job from slowing down the website.
Create the timer
Create /etc/systemd/system/backup.timer:
[Unit]
Description=Run backup daily at 02:30
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=10m
[Install]
WantedBy=timers.targetPersistent=true: if the server was down at 02:30, run the job as soon as it boots.RandomizedDelaySec: spreads start times so many servers do not hit a backup target at once.
Enable and start the timer
systemctl daemon-reload && systemctl enable --now backup.timerRun the job immediately to test it:
systemctl start backup.serviceOnCalendar examples
| Schedule | OnCalendar value |
|---|---|
| Every 15 minutes | *:0/15 |
| Hourly | hourly |
| Daily at midnight | daily |
| Mondays at 06:00 | Mon *-*-* 06:00:00 |
| Weekdays at 09:00 | Mon..Fri 09:00 |
| First of the month | *-*-01 03:00:00 |
Check how systemd interprets an expression and when it next fires:
systemd-analyze calendar 'Mon..Fri 09:00'For intervals relative to boot or last run, use OnBootSec=5min and OnUnitActiveSec=1h instead of OnCalendar.
Monitor timers and logs
- List all timers with last and next run:
systemctl list-timers - View the job's output:
journalctl -u backup.service(add-fto follow,--since todayto filter) - Check the last result:
systemctl status backup.service
For alerts on failure, add OnFailure=notify-failure@%n.service to the service's [Unit] section, pointing to a small service that sends an email or webhook.
Cron vs systemd timers
| cron | systemd timers | |
|---|---|---|
| Setup | One line | Two small files |
| Logs | Manual redirects | Automatic in journalctl |
| Missed runs | Skipped | Caught up with Persistent |
| Overlap protection | Needs flock | Built in: a running service is not started twice |
| Resource limits | No | CPU, memory, I/O limits |
Troubleshooting
- Timer is active but the job never runs: check
systemctl list-timersfor the next run and test the calendar expression. - Unit not found: run
systemctl daemon-reloadafter creating or editing unit files. - Job fails with status=203/EXEC: the script path is wrong, not executable, or missing its shebang.
- Environment variables missing: add
Environment=orEnvironmentFile=lines to the service.
Systemd timers FAQ
Can normal users create timers?
Yes, as user units in ~/.config/systemd/user/, managed with systemctl --user. Run loginctl enable-linger user so they run while the user is logged out.
Should I replace all cron jobs?
Not necessarily. Cron is fine for simple tasks; timers are worth it for jobs where logging, catch-up or limits matter.
Are timers available on cPanel hosting?
No, shared hosting accounts use cron jobs in cPanel. Timers need root access on your own server.
