systemd timers are cron's modern successor with inter-unit dependencies, centralized journald logging, and RandomizedDelaySec. This episode builds .timer + .service units, uses OnCalendar= and OnBootSec, enables Persistent=, then maps out when and how to move from cron.

In episode 17 we compared cronie with systemd timers. Now we study that rival fully — because on systemd-based distros, timers are increasingly the default choice for new tasks. systemd timers aren't just "cron, the systemd version": they bring three things cron doesn't have — inter-unit dependencies, centralized logging in journald, and built-in randomization (RandomizedDelaySec). This episode builds your first timer and maps out when it's time to switch.
Timers work in pairs: a .timer file (schedule) and a .service file (the command to run).
[Unit]
Description=Backup harian
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh[Unit]
Description=Jadwal backup harian 02.30
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timerssystemctl list-timers shows schedules, next execution time, and last run time — a much better scheduling dashboard than crontab -l.
OnCalendar= accepts expressive calendar schedules — explore the full format with systemd-analyze calendar:
OnCalendar=*-*-* 02:30:00 # setiap hari 02.30
OnCalendar=Mon-Fri 22:00:00 # hari kerja pukul 22.00
OnCalendar=*-*-1,15 00:00:00 # tanggal 1 & 15
OnCalendar=hourly # setiap jamBesides calendar schedules, systemd supports monotonic timers — counted from an event (boot, unit activation), not from the calendar clock:
OnBootSec=10min # 10 menit setelah boot
OnUnitActiveSec=1h # 1 jam setelah aktivasi terakhir
OnUnitInactiveSec=2hThis pattern can't be made in cron — useful for jobs that must run after boot without needing to know the calendar time.
Persistent=true on OnCalendar= runs jobs that were missed while the system was off — the anacron equivalent. When the machine boots and it turns out 02.30 has already passed, the timer immediately runs the service:
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=trueAfter=, Requires=, Wants= let a job wait for other services:
[Unit]
Description=Backup DB
After=postgresql.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/pg_backup.shAll service output goes into journald automatically — no manual >> log 2>&1 redirects like in cron (episode 5):
journalctl -u backup.service -n 20
journalctl -u backup.timerThe equivalent of sleep $((RANDOM % 300)) from episode 11, but built in:
[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=300OnCalendar syntax is completely different from crontab (a new learning curve), it requires systemd so it's useless in non-systemd containers or BSD, and complex schedules are still easier to read as a crontab.
Move to timers when you need inter-unit dependencies, centralized journald logging, or built-in random delay/persistent. Keep using cron on non-systemd hosts or where you want portability, and use an entrypoint loop for containers (episode 15).
Tip
Don't migrate all crontabs at once. Start with new jobs, or jobs that genuinely need timer advantages (dependencies/journald). A gradual migration lets your team learn the new syntax without risking mass downtime.
Key takeaways:
.timer file (schedule) + a .service file (command).OnCalendar= for calendar; OnBootSec/OnUnitActiveSec for monotonic.Persistent=true catches up on missed jobs (the anacron equivalent).RandomizedDelaySec for randomization.In episode 19 we'll cover distributed scheduling: advanced K8s CronJob — startingDeadlineSeconds, suspend, successfulJobsHistoryLimit, job parallelism, plus alternatives like Argo Workflows and the KEDA Cron scaler for large workloads!