Timers (.timer)

Run a job daily at a specific time (and catch up after downtime)

3 min read

OnCalendar= is systemd’s calendar syntax — more readable than cron and validatable. Here’s the daily case plus common variants.

Daily at 03:30

/etc/systemd/system/nightly.service:

[Unit]
Description=Nightly maintenance

[Service]
Type=oneshot
ExecStart=/usr/local/bin/nightly.sh

/etc/systemd/system/nightly.timer:

[Unit]
Description=Run nightly maintenance at 03:30

[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true records the last run on disk; if the machine was off at 03:30, the job runs shortly after the next boot instead of being silently skipped.

Common OnCalendar values

OnCalendar=daily                 # 00:00 every day
OnCalendar=*-*-* 03:30:00        # 03:30 every day
OnCalendar=Mon *-*-* 09:00:00    # 09:00 every Monday
OnCalendar=Mon..Fri 18:00        # 18:00 on weekdays
OnCalendar=*-*-01 04:00:00       # 04:00 on the 1st of each month
OnCalendar=*-*-* *:00:00         # top of every hour
OnCalendar=hourly                # same as above

The full form is DayOfWeek Year-Month-Day Hour:Minute:Second; * is “any”, and ranges (Mon..Fri), lists (Mon,Wed), and steps (0/5) are allowed.

Enable and verify

sudo systemctl daemon-reload
sudo systemctl enable --now nightly.timer
systemctl list-timers nightly.timer
# Test the expression without enabling anything:
systemd-analyze calendar 'Mon..Fri 18:00'

Timezone: OnCalendar uses the system local time by default; append a zone like OnCalendar=*-*-* 03:30 America/New_York, or set the unit’s Timezone=. Gotchas: enable the .timer. Use AccuracySec=1s if you need the job to fire exactly on time (systemd batches timers within a 1-minute window by default to save power).

Open the full version (with copy buttons) ↗

← All recipes