# Using systemd timers instead of cron jobs
Source: https://vpsdime.com/knowledgebase/technical-questions/systemd-timers
Last reviewed: 2026-07-05
Summary: Why systemd timers are the better scheduler for jobs that matter, every run logged in journald, no overlap by design, catch-up for missed runs (Persistent), resource caps, then the two-file recipe (service + timer), OnCalendar syntax with systemd-analyze verification, and the enable-the-timer-not-the-service gotcha.

## What this is

Every modern distro ships a second scheduler alongside cron: **systemd timers**. For quick one-liners, cron is fine. For jobs that *matter*, backups, certificate renewals, database dumps, timers fix every one of cron's classic failure modes ([the ones that fill the cron troubleshooting guide](https://vpsdime.com/knowledgebase/troubleshooting/cron-not-running)), and they're only slightly more work to set up.

## What you get over cron

- **Every run is logged.** `journalctl -u myjob.service` shows each execution with its full output and exit status, no more "did it even run?" archaeology, no MAILTO plumbing.
- **No overlapping runs, by design.** A timer won't start the service while the previous run is still going, so the [cron cascade](https://vpsdime.com/knowledgebase/technical-questions/cron-basics), runs piling onto unfinished runs until load makes the VPS unresponsive, simply can't happen. No `flock` needed.
- **Missed runs can catch up.** With `Persistent=true`, a job whose moment passed while the VPS was off or rebooting runs at the next boot, cron just skips it silently. (For a VPS that gets [suspended and reactivated](https://vpsdime.com/knowledgebase/troubleshooting/services-not-starting-after-reboot), that difference is your nightly dump actually happening.)
- **No cron environment quirks.** No `%` escaping, saner behavior, and anything the job needs can be declared explicitly in the unit.
- **Resource caps.** `CPUQuota=50%` or `MemoryMax=1G` on the service keeps a heavy backup or reindex job from [starving the site it's supposed to protect](https://vpsdime.com/knowledgebase/troubleshooting/slow-vps).
- **Ordering.** `After=network-online.target` and friends, so a job that needs the network doesn't fire before it exists.

## The recipe: two small files

A scheduled job is a **service** (what to run) plus a **timer** (when). Say it's a nightly backup script:

**`/etc/systemd/system/backup.service`**

```ini
[Unit]
Description=Nightly backup

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

**`/etc/systemd/system/backup.timer`**

```ini
[Unit]
Description=Run nightly backup at 03:30

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

[Install]
WantedBy=timers.target
```

Then activate:

```
systemctl daemon-reload
systemctl enable --now backup.timer
```

(`RandomizedDelaySec` spreads the start over a window, a good habit so all your jobs don't stampede at the same second.)

## Operating it

- **See all timers, with last and next run:** `systemctl list-timers`
- **Read a job's history and output:** `journalctl -u backup.service`
- **Run it right now** (without waiting for the schedule): `systemctl start backup.service`
- **Schedule syntax:** `OnCalendar` takes shorthands (`daily`, `hourly`, `weekly`) or full expressions (`Mon *-*-* 06:00:00`). Verify any expression before trusting it, systemd has its own crontab.guru built in:

```
systemd-analyze calendar "Mon *-*-* 06:00:00"
```

which prints exactly when it will fire next.

## The gotchas

- **Enable the `.timer`, not the `.service`.** Enabling the service makes it run at *boot*; the timer is what schedules it. (`enable --now backup.timer` is the whole incantation.)
- `Type=oneshot` is right for scripts that run and exit, which is what scheduled jobs are.
- Jobs that shouldn't run as root: add `User=deploy` to the `[Service]` section.

## When cron is still fine

A one-line log cleanup, a quick curl ping, anything where a silent miss costs nothing, crontab is faster to type and perfectly adequate ([its failure modes and fixes](https://vpsdime.com/knowledgebase/troubleshooting/cron-not-running)). The rule of thumb: **if you'd be upset to learn the job hadn't run for a month, it belongs in a timer.**

## Still need help?

You can [open a support ticket](https://vpsdime.com/open-ticket/). So we can help on the first reply, it's worth mentioning:

- the VPS hostname or IP,
- the .timer and .service files,
- what `systemctl list-timers` and the job's journalctl output show.

## Related questions

- "Should I use a systemd timer or a cron job?"
- "How do I create a systemd timer?"
- "How do I see when my timer last ran and what it printed?"
- "What does Persistent=true do?"
- "How do I verify an OnCalendar expression?"
- "How do I stop a scheduled job from overlapping itself?"
