Cron jobs are a relic of a bygone era, and systemd timers are their modern, more robust, and flexible replacement.
Let’s see one in action. Imagine you have a script, ~/bin/daily_cleanup.sh, that you want to run every day at 2:00 AM.
First, create the script itself. Make it executable:
#!/bin/bash
echo "Running daily cleanup at $(date)" >> ~/cleanup.log
# Add your actual cleanup commands here
exit 0
Now, we need two systemd unit files: a .service file to define what to run, and a .timer file to define when to run it.
Create ~/.config/systemd/user/daily_cleanup.service:
[Unit]
Description=Run daily cleanup script
[Service]
Type=oneshot
ExecStart=%h/bin/daily_cleanup.sh
The Description is for human readability. Type=oneshot is crucial here; it means the service performs a single task and then exits. ExecStart points to the script we want to run. %h is a systemd specifier that expands to the user’s home directory.
Next, create ~/.config/systemd/user/daily_cleanup.timer:
[Unit]
Description=Timer for daily cleanup script
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
OnCalendar=*-*-* 02:00:00 is the heart of the timer. It specifies a calendar event: every year (*), every month (*), every day (*), at 02:00:00. systemd’s calendar syntax is incredibly powerful and can handle complex schedules. Persistent=true is important: if the system is off when the timer is supposed to fire, it will run once as soon as possible after the system boots up.
The [Install] section with WantedBy=timers.target ensures that when you enable the timer, it gets linked into the systemd startup process.
After creating these files, you need to tell systemd to reload its configuration and then enable and start the timer:
systemctl --user daemon-reload
systemctl --user enable daily_cleanup.timer
systemctl --user start daily_cleanup.timer
--user is key here, indicating these are user-specific units, not system-wide ones.
Now, at 2:00 AM every day, systemd will automatically execute ~/bin/daily_cleanup.sh. You can check its status:
systemctl --user status daily_cleanup.timer
systemctl --user list-timers --all
The list-timers command will show you when the timer is scheduled to run next.
The problem systemd timers solve is the unreliability and lack of visibility of traditional cron. cron jobs run in isolation; if a job fails, you often don’t know unless you explicitly add logging and error checking. systemd services, on the other hand, have built-in logging via journald. You can check the output of your script with journalctl --user -u daily_cleanup.service. Timers also offer more sophisticated scheduling (e.g., running X minutes after boot, or on specific days of the week) and better resource control. The Persistent=true option is a lifesaver for jobs that must run, even if the machine was offline.
A common point of confusion is the difference between OnCalendar and OnBootSec/OnUnitActiveSec. OnCalendar is for absolute times (like "every Tuesday at 3 PM"), whereas OnBootSec and OnUnitActiveSec are relative to system boot or the activation of another unit, respectively. If you’re migrating from cron, OnCalendar is almost always what you want.
The next thing you’ll want to figure out is how to handle dependencies between timers and services, or how to set up timers that trigger other services.