Skip to content
Docs

Migrating from Cron Jobs to Schedules

Cron Jobs are a legacy feature and will be migrated to Schedules in the future. You can migrate now with a configuration change. Your route code keeps working as it is.

After you migrate, you can:

  • Set a timezone for each schedule instead of using UTC
  • Enable, disable, or run each schedule without redeploying
  • Run schedules in Preview branches and custom environments
  • See the execution history of each schedule
  • Switch to a private function or a queue topic later

A schedule with a path target calls your route the same way a Cron Job does:

  • The user-agent header is vercel-cron/1.0.
  • The x-vercel-cron-schedule header contains the cron expression.
  • If you set CRON_SECRET, the Authorization header contains Bearer <CRON_SECRET>, so your CRON_SECRET check keeps working.

Path schedules send a POST request by default, but Cron Jobs send GET. Set requestMethod to GET when you migrate so your route handler doesn't need to change.

Schedule requests also include schedule headers, such as the execution ID and scheduled time.

  1. Cron Jobs live in the crons property of vercel.json:

    vercel.json
    {
      "$schema": "https://openapi.vercel.sh/vercel.json",
      "crons": [
        { "path": "/api/daily-report", "schedule": "0 9 * * *" },
        { "path": "/api/cleanup", "schedule": "0 * * * *" }
      ]
    }

    You can also see them on the Cron Jobs tab of your project's Schedules page in the dashboard.

  2. For each Cron Job, add an entry to schedules. Use a name as the key, keep the same path and schedule, and add "requestMethod": "GET":

    vercel.json
    {
      "$schema": "https://openapi.vercel.sh/vercel.json",
      "schedules": {
        "daily-report": {
          "schedule": "0 9 * * *",
          "path": "/api/daily-report",
          "requestMethod": "GET"
        },
        "cleanup": {
          "schedule": "0 * * * *",
          "path": "/api/cleanup",
          "requestMethod": "GET"
        }
      }
    }

    Remove the crons entries in the same change. If you keep both, your route runs twice for each occurrence.

    Cron Jobs always run in UTC, and schedules default to UTC. Leave out timezone to keep the same run times. If you add a timezone, update the cron expression so the schedule still runs when you expect.

  3. Deploy your project:

    terminal
    vercel deploy --prod

    Edgenivo creates the schedules when the deployment becomes your production deployment, and stops running the Cron Jobs you removed.

    1. Open your project in the Edgenivo dashboard and select Schedules.
    2. Check that your schedules appear in the list.
    3. Select a schedule and click Run Schedule to test it without waiting for its next run.

    You can also run a schedule from the CLI:

    terminal
    vercel schedules invoke daily-report

Schedules start within a jitter window instead of at an exact time. The limits also differ by plan:

PlanCron JobsSchedules
HobbyAt most once per day, at any time within the scheduled hourAt most once every 15 minutes, within a 15-minute jitter window
Pro and EnterpriseAt the scheduled minuteWithin the jitter window, which defaults to 1 minute and can be set from 1 to 15 minutes

On Pro and Enterprise, add jitter to change the window:

vercel.json
{
  "schedules": {
    "cleanup": {
      "schedule": "0 * * * *",
      "path": "/api/cleanup",
      "requestMethod": "GET",
      "jitter": 5
    }
  }
}

Disable Cron Jobs on the Cron Jobs tab of the Schedules page only pauses Cron Jobs. It doesn't affect schedules. To pause a schedule, select it and choose Disable from the menu next to Run Schedule, or run:

terminal
vercel schedules disable cleanup

Disabling a static schedule doesn't require a redeploy. See Managing schedules.

A Cron Job path is a public URL, so anyone can call it unless you check CRON_SECRET. After you migrate, you can switch to a function target. Function targets have no public URL, and only Edgenivo's scheduler can invoke them.

  1. Install @vercel/schedules and wrap your handler in handleSchedule.
  2. Add a schedule/v1beta trigger to the function.
  3. Replace path and requestMethod with function.

See Function targets for the full steps. Keep the same schedule name so Edgenivo updates the existing schedule instead of creating a new one.

Last updated October 9, 2026

Was this helpful?

supported.