> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://rivet.chrisboulton.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://rivet.chrisboulton.com/_mcp/server.

# Timer

> Manage a systemd timer and its paired oneshot service.

```hcl
timer "cleanup" {
  user             = "root"
  command          = ["/usr/local/bin/example-cleanup"]
  on_calendar      = "*-*-* 03:15:00"
  persistent       = true
  randomized_delay = "5m"
  accuracy         = "1s"
  enabled          = true
}
```

A `timer` runs a command on a schedule using systemd. Rivet writes two units to `/etc/systemd/system`: a `.timer` and the oneshot `.service` it starts, both root-owned with mode 0644. `name` sets the unit name; without it, Rivet derives a stable name from the resource's address. Other resources can't manage either unit file, and unmanaged drop-ins, masks, or aliases for them are refused so nothing overrides the job.

## Schedule and command

A present timer needs a `command`, as a list of arguments with an absolute executable, and at least one trigger:

* `on_calendar`: a systemd calendar expression, checked on the host with `systemd-analyze calendar`.
* `on_boot`, `on_active`, `on_unit_active`, `on_unit_inactive`: durations relative to boot, timer activation, or the service's last start or stop.

Several triggers combine the way systemd combines them. Durations are positive integers with `ms`, `s`, `m`, or `h`; `randomized_delay` also accepts `0s`. `persistent = true` runs a missed calendar job at the next opportunity and needs `on_calendar`.

The command runs as `user` (default root), with optional `group`, `cwd` (default `/`), `environment`, and `umask` (default `0022`). Arguments are passed as-is: systemd `%` specifiers are escaped, and there is no shell or variable expansion; to use a shell, make it the command, as in `["/bin/sh", "-c", "..."]`.

## Enabling and updating

`enabled = true` means the timer is active now and starts at boot. `false` stops and disables it but keeps its unit files. When a definition changes, Rivet stops the timer, writes the new units, reloads systemd, and restores the requested state. Unchanged units aren't rewritten, and Rivet never runs the job itself or stops a job that is running. Systemd prevents overlapping runs of the job.

`ensure = "absent"` disables the timer, removes both units, and reloads systemd. It refuses while the job is running. Systemd's record of past runs is kept.

If a run is interrupted, the next one restores both units and the timer's state without running the job.

## Attributes

| Attribute          | Default                 | Behavior                                                             |
| ------------------ | ----------------------- | -------------------------------------------------------------------- |
| `name`             | Derived from logical ID | Normalized `.timer` name; owns the paired same-stem `.service`.      |
| `ensure`           | `present`               | `present` or `absent`.                                               |
| `enabled`          | `true`                  | Active now and enabled at boot; false stops and disables scheduling. |
| `command`          | Required when present   | Nonempty argv with an absolute executable.                           |
| `user`             | `root`                  | Local account name.                                                  |
| `group`            | User's primary group    | Optional local group name; null omits it.                            |
| `cwd`              | `/`                     | Absolute working directory.                                          |
| `environment`      | Empty                   | String-valued environment map.                                       |
| `umask`            | `0022`                  | Octal string.                                                        |
| `on_calendar`      | Omitted                 | Native calendar schedule, checked on the target.                     |
| `on_boot`          | Omitted                 | Duration since boot.                                                 |
| `on_active`        | Omitted                 | Duration since timer activation.                                     |
| `on_unit_active`   | Omitted                 | Duration since the paired service last activated.                    |
| `on_unit_inactive` | Omitted                 | Duration since the paired service last became inactive.              |
| `persistent`       | `false`                 | Calendar catch-up policy; requires on_calendar.                      |
| `randomized_delay` | `0s`                    | Random delay; 0s is allowed.                                         |
| `accuracy`         | `1s`                    | Positive schedule accuracy duration.                                 |

`user` and `group` take names, not numeric IDs, and managed users and groups are ordered before the timer. All [common metadata](/language/metadata) applies, but timers can't be notification targets.

The default timeout is 120 seconds. Timers need `timer.systemd.v1` and `refresh.journal.v1`. Static exports are `name`, `ensure`, and `meta.sensitive`. There is no cron resource; use timers instead.