---
title: "Crons"
description: "Sentry Crons allows you to monitor the uptime and performance of any scheduled, recurring job in your application using the Sentry JavaScript SDK."
url: https://docs.sentry.io/platforms/javascript/guides/cloudflare/crons/
---

# Set Up Crons for Cloudflare

Once implemented, it'll allow you to get alerts and metrics to help you solve errors, detect timeouts, and prevent disruptions to your service.

## [Requirements](https://docs.sentry.io/platforms/javascript/guides/cloudflare/crons.md#requirements)

* Use our [getting started](https://docs.sentry.io/platforms/javascript/guides/cloudflare.md) guide to install and configure the Sentry SDK (version `7.51.1` or newer) for your recurring job.
* Send a monitor configuration from your code as shown below, and Sentry creates the monitor on the first check-in. You can also [create the monitor in Sentry](https://sentry.io/issues/alerts/new/crons/) instead.

## [Cron Triggers](https://docs.sentry.io/platforms/javascript/guides/cloudflare/crons.md#cron-triggers)

Available since: `v11.7.0`

If your Worker runs on [Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/), add `cronTriggersIntegration` to send check-ins for every run of the `scheduled` handler. The in-progress check-in of each run carries the trigger's cron expression as the schedule, so Sentry creates the monitor on the first run. The integration isn't enabled by default because each monitor it creates is billed.

With the [Sentry Cloudflare Vite plugin](https://docs.sentry.io/platforms/javascript/guides/cloudflare/install/vite-plugin.md), add the integration to your `instrument.server.*` file. Cron Triggers have no names, and the `scheduled` handler only receives the cron expression, so the setup uses three files. First, keep a map from each expression in your wrangler config to its monitor slug in its own module:

```typescript
export const monitorSlugs: Record<string, string> = {
  "30 9 * * MON-FRI": "daily-report",
  "0 */6 * * *": "sync-inventory",
};
```

Then use the map in `instrument.server.ts` for the slugs:

```typescript
import * as Sentry from "@sentry/cloudflare";
import { monitorSlugs } from "./monitorSlugs";

export default Sentry.defineCloudflareOptions((env) => ({
  dsn: env.SENTRY_DSN,
  integrations: [
    Sentry.cronTriggersIntegration({
      monitorSlug: (cron) => monitorSlugs[cron],
    }),
  ],
}));
```

And in the `scheduled` handler of your Worker to select the job:

```typescript
import { monitorSlugs } from "./monitorSlugs";

export default {
  async scheduled(controller, env, ctx) {
    switch (monitorSlugs[controller.cron]) {
      case "daily-report":
        // Your daily report job
        break;
      case "sync-inventory":
        // Your inventory sync job
        break;
    }
  },
};
```

If you deploy with `wrangler` directly, pass the same integration to `Sentry.withSentry` in your Worker, as shown in the [Wrangler setup](https://docs.sentry.io/platforms/javascript/guides/cloudflare/install/wrangler.md#configure).

When you change a schedule in your wrangler config, change its key in `monitorSlugs`. The monitor keeps its slug, and Sentry updates its schedule on the next check-in. Triggers without an entry send no check-ins. The `monitorSlug` function can also return an object with the slug and other monitor options, such as `{ monitorSlug: "daily-report", maxRuntime: 30 }`.

Without a `monitorSlug` function, the slug comes from the cron expression: `30 9 * * MON-FRI` becomes `cron-30-9-x-x-montofri` (`*` is written as `x`, `,` as `_`, `-` as `to`, and `/` as `by`). Expressions with other characters, or slugs longer than 50 characters, end in a hash of the expression. Set a `monitorSlug` function in these cases:

* You change a schedule. A new expression gives a new slug, so Sentry creates a new monitor. The old monitor gets no more check-ins and reports missed check-ins, so delete it in Sentry.
* Two or more Workers report to the same project and environment with the same cron expression. Without a `monitorSlug` function, these Workers share one monitor, and the check-ins of one Worker can hide the missed runs of another.

Cloudflare numbers the days of the week from 1 (Sunday) to 7 (Saturday), and Sentry numbers them from 0 (Sunday). The SDK converts the day-of-week field to day names before sending it, so `1-5` is sent as `SUN-THU`. For Monday to Friday, use `2-6` or `MON-FRI`. If the day-of-week field can't be converted, the day-of-month field has `W`, `?`, or `L` with an offset (such as `L-3`), or both day fields are set and one of them starts with `*` (such as `*/2`), check-ins are sent without a schedule or other monitor options, and you need to create the monitor in Sentry first.

Runs without a cron expression, such as some manual runs with `--test-scheduled`, send no check-ins.

## [Job Monitoring](https://docs.sentry.io/platforms/javascript/guides/cloudflare/crons.md#job-monitoring)

Use the `Sentry.withMonitor()` API to monitor a callback and notify you if your periodic task is missed (or doesn't start when expected), if it fails due to a problem in the runtime (such as an error), or if it fails by exceeding its maximum runtime.

`Sentry.withMonitor()` requires SDK version `7.76.0`.

Pass a monitor configuration as the third argument so Sentry creates or updates the monitor from your code:

```javascript
Sentry.withMonitor(
  "<monitor-slug>",
  () => {
    // Execute your scheduled task here...
  },
  {
    schedule: { type: "crontab", value: "0 * * * *" },
  },
);
```

If the monitor already exists in Sentry, the configuration is optional. See [Upserting Cron Monitors](https://docs.sentry.io/platforms/javascript/guides/cloudflare/crons.md#upserting-cron-monitors) for all configuration options.

`Sentry.withMonitor()` does not create new a trace. Events and errors that occur within the monitored callback will be associated with the current trace context.

If you are using an SDK version prior to `7.76.0`, you can use the `Sentry.captureCheckIn()` API documented below.

## [Check-Ins](https://docs.sentry.io/platforms/javascript/guides/cloudflare/crons.md#check-ins)

Check-in monitoring allows you to track a job's progress by completing two check-ins: one at the start of your job and another at the end of your job. This two-step process allows Sentry to notify you if your job didn't start when expected (missed) or if it exceeded its maximum runtime (failed).

```javascript
// 🟡 Notify Sentry your job is running:
const checkInId = Sentry.captureCheckIn({
  monitorSlug: "<monitor-slug>",
  status: "in_progress",
});

// Execute your scheduled task here...

// 🟢 Notify Sentry your job has completed successfully:
Sentry.captureCheckIn({
  // Make sure this variable is named `checkInId`
  checkInId,
  monitorSlug: "<monitor-slug>",
  status: "ok",
});
```

If your job execution fails, you can notify Sentry about the failure:

```javascript
// 🔴 Notify Sentry your job has failed:
Sentry.captureCheckIn({
  // Make sure this variable is named `checkInId`
  checkInId,
  monitorSlug: "<monitor-slug>",
  status: "error",
});
```

## [Heartbeat](https://docs.sentry.io/platforms/javascript/guides/cloudflare/crons.md#heartbeat)

Heartbeat monitoring notifies Sentry of a job's status through one check-in. This setup will only notify you if your job didn't start when expected (missed). If you need to track a job to see if it exceeded its maximum runtime (failed), use check-ins instead.

```javascript
// Execute your scheduled task...

// 🟢 Notify Sentry your job completed successfully:
Sentry.captureCheckIn({
  monitorSlug: "<monitor-slug>",
  status: "ok",
});
```

If your job execution fails, you can:

```javascript
// 🔴 Notify Sentry your job has failed:
Sentry.captureCheckIn({
  monitorSlug: "<monitor-slug>",
  status: "error",
});
```

## [Upserting Cron Monitors](https://docs.sentry.io/platforms/javascript/guides/cloudflare/crons.md#upserting-cron-monitors)

You can create and update your Monitors programmatically with code rather than [creating and configuring them in Sentry.io](https://sentry.io/issues/alerts/new/crons/).

To create/update a monitor, use `Sentry.withMonitor()` and pass in your monitor configuration as a third parameter:

```javascript
const monitorConfig = {
  schedule: {
    type: "crontab",
    value: "* * * * *",
  },
  checkinMargin: 2, // In minutes. Optional.
  maxRuntime: 10, // In minutes. Optional.
  timezone: "America/Los_Angeles", // Optional.
};

Sentry.withMonitor(
  "<monitor-slug>",
  () => {
    // Execute your scheduled task here...
  },
  monitorConfig,
);
```

To configure the monitor's check-ins, use `Sentry.captureCheckIn()` and pass in your monitor configuration as a second parameter:

```javascript
const monitorConfig = {
  schedule: {
    type: "crontab",
    value: "* * * * *",
  },
  checkinMargin: 2, // In minutes. Optional.
  maxRuntime: 10, // In minutes. Optional.
  timezone: "America/Los_Angeles", // Optional.
};

// 🟡 Notify Sentry your job is running:
const checkInId = Sentry.captureCheckIn(
  {
    monitorSlug: "<monitor-slug>",
    status: "in_progress",
  },
  monitorConfig,
);

// Execute your scheduled task here...

// 🟢 Notify Sentry your job has completed successfully:
Sentry.captureCheckIn(
  {
    // Make sure this variable is named `checkInId`
    checkInId,
    monitorSlug: "<monitor-slug>",
    status: "ok",
  },
  monitorConfig,
);
```

### [Monitor Configuration Properties](https://docs.sentry.io/platforms/javascript/guides/cloudflare/crons.md#monitor-configuration-properties)

The following are available monitor configuration properties:

`schedule`:

The job's schedule:

The schedule representation for your monitor, either `crontab` or `interval`. The structure will vary depending on the type:

```json
{"type": "crontab", "value": "0 * * * *"}
{"type": "interval", "value": 2, "unit": "hour"}
```

`checkinMargin`:

The amount of time (in minutes) Sentry should wait for your check-in before it's considered missed ("grace period"). Optional.

We recommend that your check-in margin be less than or equal to your interval.

`maxRuntime`:

The amount of time (in minutes) your job is allowed to run before it's considered failed. The maximum is 10080 minutes (7 days). Optional.

`timezone`:

The `tz` where your job is running. This is usually your server's timezone, (such as `America/Los_Angeles`). See [list of tz database time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). Optional.

`node-cron` allows you to pass luxon timezones, but you have to convert them to an IANA timezone when using Sentry (for example, using `DateTime.local().zoneName`).

`failureIssueThreshold`:

*requires SDK version `8.7.0` or higher*

The number of consecutive failed check-ins required before an issue can be created. Optional.

`recoveryThreshold`:

*requires SDK version `8.7.0` or higher*

The number of consecutive successful check-ins required for an issue to be considered resolved. Optional.

## [Alerts](https://docs.sentry.io/platforms/javascript/guides/cloudflare/crons.md#alerts)

When your recurring job fails to check in (missed), runs beyond its configured maximum runtime (failed), or manually reports a failure, Sentry will create an error event with a tag to your monitor.

To receive alerts about these events:

1. Navigate to **Alerts** in the sidebar.
2. Create a new alert and select "Issues" under "Errors" as the alert type.
3. Configure your alert and define a filter match to use: `The event's tags match {key} {match} {value}`.

Example: `The event's tags match monitor.slug equals my-monitor-slug-here`

Learn more in [Alerts](https://docs.sentry.io/product/monitors-and-alerts/alerts.md).

## [Rate Limits](https://docs.sentry.io/platforms/javascript/guides/cloudflare/crons.md#rate-limits)

To prevent abuse and resource overuse, Crons limits check-ins to **6 per minute for each monitor environment**.

For example, if you have a monitor called "database-backup" with two environments:

* `database-backup` in environment `production` can send up to 6 check-ins per minute
* `database-backup` in environment `staging` can also send up to 6 check-ins per minute
* Combined, they can send up to 12 check-ins per minute

You can verify if any check-ins are being dropped by visiting the [Usage Stats](https://docs.sentry.io/product/stats.md#usage-stats) page. To avoid dropped check-ins, ensure your monitors don't exceed the rate limit.

## Pages in this section

- [Troubleshooting](https://docs.sentry.io/platforms/javascript/guides/cloudflare/crons/troubleshooting.md)
