---
url: https://rustrak.dev/usage/crons
---

# Crons

Errors tell you when code breaks. They cannot tell you when code never ran: a nightly backup whose cron line was deleted, a worker that died, a job hung for six hours. Crons watches your scheduled jobs and opens an issue when a run is missed, fails or runs too long.

It is compatible with Sentry Crons: the `check_in` item Sentry SDKs send, the HTTP check-in endpoint and `sentry-cli monitors run` all work against Rustrak unchanged.

## How it works

Each job has a monitor, named by a slug. Every run sends check-ins: `in_progress` when it starts, then `ok` or `error` when it ends, with the same check-in ID.

From the monitor's schedule Rustrak knows when the next run is due. Once a minute it records what the job did not report itself:

| Status | Meaning |
|---|---|
| Missed | No check-in arrived within the grace period after the expected time |
| Timed out | A run stayed `in_progress` longer than its max runtime |
| Failed | The job reported `error` |

After as many consecutive failures as the failure tolerance, an issue opens, titled `Cron failure: <monitor>`. It goes through the same pipeline as any error, so your [alerts](https://rustrak.dev/usage/alerts) fire for it, and later failures land in the same issue. After as many consecutive `ok` check-ins as the recovery tolerance, the issue resolves itself.

Each environment a job checks in from (`production`, `staging`) is tracked apart, with its own status and its own issue.

## Create a monitor

**From code.** Send a `monitor_config` with the check-in. The monitor is created on the job's first run and updated whenever the config changes:

```python
from sentry_sdk.crons import monitor

monitor_config = {
    "schedule": {"type": "crontab", "value": "0 3 * * *"},
    "timezone": "Europe/Madrid",
    "checkin_margin": 5,   # minutes
    "max_runtime": 30,     # minutes
}

@monitor(monitor_slug="nightly-backup", monitor_config=monitor_config)
def nightly_backup():
    ...
```

```javascript
Sentry.withMonitor(
  'nightly-backup',
  async () => {
    // your job
  },
  { schedule: { type: 'crontab', value: '0 3 * * *' }, checkinMargin: 5, maxRuntime: 30 },
);
```

**From the dashboard.** Open a project, go to **Crons** and click **New monitor**. The form previews the next runs in the monitor's timezone as you type the schedule. Until the first check-in arrives, the monitor's page shows the snippets below with your DSN filled in.

## Check in without an SDK

Any job that can run `curl` can check in. The URL carries the project ID, the slug and the DSN's public key:

```bash
RUSTRAK_CRONS="https://rustrak.example.com/api/<project_id>/cron/<monitor-slug>/<dsn_key>/"

curl "$RUSTRAK_CRONS?status=in_progress"
./run-backup.sh && curl "$RUSTRAK_CRONS?status=ok" || curl "$RUSTRAK_CRONS?status=error"
```

`GET` and `POST` both work. The query string takes `status` (required), `check_in_id`, `environment` and `duration` in seconds. To create the monitor from a check-in, `POST` a JSON body with a `monitor_config` and `Content-Type: application/json`. Without that header the body is ignored:

```bash
curl -X POST "$RUSTRAK_CRONS" \
  -H "Content-Type: application/json" \
  -d '{"status": "ok", "monitor_config": {"schedule": {"type": "crontab", "value": "0 3 * * *"}}}'
```

The endpoint answers `202 Accepted`. A check-in for a monitor that does not exist, sent without a `monitor_config`, gets a `404` saying so.

If you pass your own `check_in_id`, use a random UUID (version 4), as the SDKs do.

`sentry-cli monitors run` wraps a command and reports its exit code:

```bash
export SENTRY_DSN=https://<dsn_key>@rustrak.example.com/<project_id>
sentry-cli monitors run --schedule "0 3 * * *" nightly-backup -- ./run-backup.sh
```

## Settings

| Setting | Default | Meaning |
|---|---|---|
| Schedule | | A five-field crontab (`0 3 * * *`), an alias such as `@daily`, or an interval (every 2 hours) |
| Timezone | `UTC` | The zone the crontab is read in, so `0 9 * * *` stays at 09:00 across daylight saving time |
| Grace period | 1 minute | How late a check-in may arrive before the run counts as missed. At most 28 days |
| Max runtime | 30 minutes | How long a run may stay in progress before it times out. At most 7 days |
| Failure tolerance | 1 | Consecutive failed check-ins before an issue opens, 1 to 720 |
| Recovery tolerance | 1 | Consecutive successful check-ins before it resolves, 1 to 720 |

A config sent from code overwrites only the fields it includes. The rest keep what was set in the dashboard.

## Mute a monitor

Click **Mute** on the monitor's page. A muted monitor keeps recording check-ins and its status, but opens no issue and sends no alert. Use it during maintenance windows; **Unmute** turns it back on.

Check-ins are kept for 90 days.
