A systemd timer can run commands, scripts, and routine maintenance jobs on Linux. It fills much the same role as cron, but adds built-in logging, dependency handling, support for missed runs, and randomized execution delays.
The Short Version
To create a systemd timer, write a .service unit with the command, then add a matching .timer unit with the schedule. Reload systemd and enable the timer.
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers --allFor instance, OnCalendar=*-*-* 02:30:00 schedules the associated service for 2:30 a.m. every day. If you add Persistent=true, systemd can trigger a missed calendar run once the computer starts again.
What This Guide Covers
- How timer and service units work together
- Scheduling a script with
OnCalendar - Testing a calendar expression before you use it
- Checking timer status and service logs
- Creating timers for a regular user account
Before You Start
You’ll need a Linux distribution that runs systemd, such as Ubuntu, Debian, Fedora, Rocky Linux, or Arch Linux. Sudo access is required for system-wide units.
First, check that systemd is available:
systemctl --versionStep 1 — Write the Task Script
The example below creates a compressed backup of /etc. Keep the script separate from the unit file. That way, you can test the actual job on its own before a timer gets involved.
sudo nano /usr/local/bin/backup-etc.shAdd this script:
#!/bin/bash
set -e
mkdir -p /var/backups/etc
/usr/bin/tar -czf /var/backups/etc/etc-$(/usr/bin/date +%F).tar.gz /etcMake the file executable, then run it once by hand:
sudo chmod 750 /usr/local/bin/backup-etc.sh
sudo /usr/local/bin/backup-etc.shBefore setting a schedule, confirm that the archive appears in /var/backups/etc. Better to catch a script problem now than at 2:30 in the morning.
Step 2 — Add the systemd Service
The timer usually doesn’t hold the task itself. It activates a service unit, and that service runs the command.
sudo nano /etc/systemd/system/backup-etc.serviceUse the following configuration:
[Unit]
Description=Create a compressed backup of the etc directory
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-etc.sh
User=rootWith Type=oneshot, systemd expects the process to carry out one job and exit. Keep paths absolute in both scripts and unit files. Services don’t inherit the same environment you get in an interactive terminal, which can make a command that worked at the prompt fail under systemd.
Step 3 — Build the Timer Unit
sudo nano /etc/systemd/system/backup-etc.timerPut this in the file:
[Unit]
Description=Run the etc backup every day
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=5m
Unit=backup-etc.service
[Install]
WantedBy=timers.target| Directive | What it does |
|---|---|
OnCalendar | Sets the calendar schedule. |
Persistent | Runs a missed calendar task after the system becomes available again. |
RandomizedDelaySec | Adds a random delay so a large group of jobs doesn’t start at once. |
Unit | Names the service that the timer activates. |
Step 4 — Check the Schedule
Before enabling anything, run systemd-analyze calendar. It checks the expression and shows the next scheduled execution time:
systemd-analyze calendar '*-*-* 02:30:00'This is a quick way to catch a malformed expression before it reaches the timer.
Common OnCalendar Expressions
| Schedule | Expression |
|---|---|
| Every day at 2:30 a.m. | *-*-* 02:30:00 |
| Every hour | hourly |
| Monday through Friday at 6 p.m. | Mon..Fri 18:00 |
| Every 15 minutes | *:0/15 |
| First day of each month | *-*-01 03:00:00 |
Step 5 — Start and Enable the Timer
Reload the unit definitions first. Then enable the timer and start it in the current session:
sudo systemctl daemon-reload
sudo systemctl enable --now backup-etc.timerEnabling the timer makes it start on later boots. The --now flag handles the current session too.
Don’t enable the oneshot service. Enable the .timer unit instead. The timer starts the service when its scheduled time arrives.
Verify the Timer and Run a Test
To see active and inactive timers, along with their next run times, use:
systemctl list-timers --allYou can inspect this timer on its own as well:
systemctl status backup-etc.timerThere’s no reason to wait until 2:30 a.m. for a test. Start the service manually, then check its status:
sudo systemctl start backup-etc.service
systemctl status backup-etc.serviceThe systemd journal holds the service logs:
journalctl -u backup-etc.service
journalctl -u backup-etc.service --since todayAfter a successful oneshot service finishes, its status may read inactive (dead). That’s normal as long as the exit status shows success.
Editing or Removing the Timer
If you change either unit file, reload systemd and restart the timer so the new definition takes effect:
sudo systemctl daemon-reload
sudo systemctl restart backup-etc.timerTo stop the timer, disable it, and remove both unit files, run:
sudo systemctl disable --now backup-etc.timer
sudo rm /etc/systemd/system/backup-etc.timer
sudo rm /etc/systemd/system/backup-etc.service
sudo systemctl daemon-reloadRunning a Timer Under Your User Account
A task that doesn’t need root privileges can run under your regular account. Store its units in ~/.config/systemd/user/, then include --user with the systemctl commands:
mkdir -p ~/.config/systemd/user
systemctl --user daemon-reload
systemctl --user enable --now my-task.timer
systemctl --user list-timersUser timers generally depend on that user’s systemd session. If the timer needs to run while the user is logged out, an administrator can enable lingering:
sudo loginctl enable-linger usernameMistakes That Cause Trouble
- Using relative paths: Give commands, scripts, and output files their full paths.
- Putting shell syntax directly in ExecStart: Pipes, redirection, and variables aren’t interpreted by a shell automatically. Put the complicated parts in a script.
- Skipping daemon-reload: New or edited unit definitions won’t be recognized until systemd reloads them.
- Looking only at the timer log: The timer records activation details, while task output and most failures appear under the service.
- Treating inactive as a failure: A oneshot service normally becomes inactive after it completes.
Practical systemd Timer Habits
- Run the command or script manually before attaching it to a timer.
- Check calendar expressions with
systemd-analyze calendar. - Set
Persistent=truefor calendar jobs that should catch up after downtime. - Use a randomized delay for update checks or jobs spread across a fleet.
- Run the service as an unprivileged user if it doesn’t need root access.
- Keep resource limits, security settings, and dependencies in the service unit rather than folding them into the schedule.





