Skip to main content

Systemd timers: the modern cron alternative you needed

Rogelio Guerra Riverón
Author
Rogelio Guerra Riverón
Building my own web infrastructure from scratch. Here I document each step: servers, networks, containers and everything that comes along.

Why I Left Cron
#

I’ve been using cron on my servers for years. It’s simple, reliable, and it works. But recently I discovered systemd timers and I’m not going back. The main reason: integrated logs in journald, no .log files scattered around the system, and better control over what happens when the server starts or reboots.

In my specific case, I had a backup that wouldn’t run if the server was off at the scheduled time. With cron, it simply got lost. With systemd timers and Persistent=true, the task runs as soon as the server boots up.

Basic structure: .service + .timer
#

Systemd needs two files:

The service (/etc/systemd/system/mibackup.service):

[Unit]
Description=Backup diario de datos
After=network.target

[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup.sh
StandardOutput=journal
StandardError=journal

The timer (/etc/systemd/system/mibackup.timer):

[Unit]
Description=Ejecuta backup diariamente

[Timer]
OnCalendar=daily
OnCalendar=*-*-* 03:00:00
Persistent=true
Accuracy=1min

[Install]
WantedBy=timers.target

Then:

sudo systemctl daemon-reload
sudo systemctl enable --now mibackup.timer

OnCalendar: the syntax you need
#

OnCalendar is systemd’s cron, but more readable:

  • daily → every day at midnight
  • weekly → every Monday at midnight
  • hourly → every hour
  • *-*-* 03:00:00 → every day at 3 AM
  • Mon *-*-* 14:30:00 → every Monday at 14:30
  • *-01,04,07,10-01 00:00:00 → first day of every quarter

You can combine multiple OnCalendar lines:

OnCalendar=*-*-* 03:00:00
OnCalendar=*-*-* 15:00:00

This runs the task twice a day.

Persistent=true: the change that convinced me
#

By default, if your server is off when a task is scheduled, systemd simply ignores it. With Persistent=true, systemd remembers and runs the task the next time it boots.

On my home server, this is critical. It’s not always on, and I need to guarantee that my backups run even if hours have passed.

[Timer]
OnCalendar=daily
Persistent=true

Type=oneshot: for tasks that finish
#

The Type=oneshot parameter in the .service indicates that the process will terminate. It’s normal for backup scripts, synchronization, etc.

If you use Type=simple (the default), systemd expects the process to keep running. That’s not what we want here.

View logs without external files
#

Here’s the best part: forget about >> /var/log/mibackup.log.

Logs go straight to journald:

# Ver los últimos logs del timer
journalctl -u mibackup.service -n 50

# Ver en tiempo real
journalctl -u mibackup.service -f

# Los últimos 2 horas
journalctl -u mibackup.service --since "2 hours ago"

# Con niveles de prioridad
journalctl -u mibackup.service -p err

Inside your script you can log with echo or logger:

#!/bin/bash
logger "Iniciando backup..."
/backup/script.sh
logger "Backup completado"

Everything is captured automatically.

Checklist of what we did
#

  1. ✅ You created a .service with Type=oneshot
  2. ✅ You created a .timer with OnCalendar and Persistent=true
  3. ✅ You reloaded systemd and activated the timer
  4. ✅ You verify logs with journalctl, no extra log files

Final tip
#

Before relying on a timer, test it manually:

# Ejecutar el servicio ahora
sudo systemctl start mibackup.service

# Ver qué pasó
journalctl -u mibackup.service -n 20

That’s it. Systemd timers isn’t a panacea, but for scheduled tasks on modern servers, it’s superior to cron in almost every way. Centralized logs in journald, combined with Persistent=true, make it impossible not to recommend it.

On my home server, all backups, cache cleanups, and data synchronizations use timers. Zero loose log files. Everything integrated.


Recommended Equipment#

Affiliate links. No extra cost for you.