Vercel cron limits, and how to get around them

Vercel Cron Jobs take one line in vercel.json, but on the Hobby plan they run at most once a day, somewhere within the hour. Here are the limits per plan, and three ways to run the same endpoint as often as you need.

CronEngine team

· 7 min read

A cron job on Vercel is not a separate kind of thing. Vercel sends a GET request to a path on your production deployment, on a schedule, and the function behind that path does the work. That makes it easy to set up, and it also means the schedule is the only part Vercel adds. If the schedule does not fit, you can keep the function and change who calls it.

In short

  1. 1On Hobby, Vercel cron runs at most once a day, anywhere within the hour you pick. Pro runs every minute, on the minute.
  2. 2Keep the endpoint, protect it with a secret, and let something outside Vercel call it as often as you need.
  3. 3Either way, make the job safe to run twice: Vercel does not retry, and a run can be missed or repeated.

The limits per plan

These are the numbers from Vercel's documentation in October 2026. Cron jobs are included on every plan, and every run is billed as an ordinary function invocation.

HobbyPro and Enterprise
Cron jobs per project100100
How oftenOnce a day at mostEvery minute
When it startsAnywhere in the hour (±59 min)Within the minute
Longest run300 seconds800 seconds
TimezoneUTCUTC

The longest run is the function's maximum duration with Fluid compute, which is on by default for new projects. Pro and Enterprise can go up to 1,800 seconds as a beta.

What the Hobby limits mean

Two rules apply on Hobby, and both catch people out.

  • Once a day, or the deployment fails. An expression such as 0 * * * * or */30 * * * * is refused at deploy time with Hobby accounts are limited to daily cron jobs.
  • Somewhere in the hour. Vercel spreads Hobby jobs out, so 0 8 * * * can start at any moment between 08:00 and 08:59 UTC. A daily report "at 8" may arrive at 08:57.

For a nightly cleanup that is fine. For anything that has to happen more often, or at a set time, it is not. Keep in mind that Vercel meant Hobby for personal, non-commercial projects in the first place.

Limits on every plan

These apply on Pro and Enterprise too, so they are worth knowing before you pay for the upgrade.

  • No retries. If a run fails, Vercel does not try again. You find out in the logs, if you look.
  • Best-effort delivery. Vercel's docs say a run can occasionally be missed, and the same run can occasionally arrive twice.
  • Overlapping runs. If a run takes longer than the interval, the next one starts anyway. Vercel suggests adding a lock yourself.
  • UTC only. No timezone setting, so daylight saving time moves your local run time twice a year.
  • Plain cron syntax. No names such as MON or JAN, and day of month and day of week cannot both be set.
  • No redirects. A 3xx answer ends the run. A cron path that redirects, for example to add a trailing slash or a locale, never reaches your code.
  • Production only. Preview deployments never receive cron requests.

Option 1: Move to Pro

If the project is already a business, this is the simplest answer: the same vercel.json, every minute, on the minute. It does not change the other limits above, so add retries, a lock and alerts yourself if the job matters.

vercel.json JSON
{
  "crons": [
    { "path": "/api/cron/sync", "schedule": "*/5 * * * *" }
  ]
}

Option 2: Call the endpoint from outside

Your cron route is a normal URL. Anything that can make an HTTPS request can call it on any schedule, and Vercel handles it like any other request to your function. That keeps you on Hobby, or gives a Pro project retries, alerts and run history without writing them.

1. Protect the route with a secret

Add an environment variable called CRON_SECRET to the project, with a random value of at least 16 characters. Vercel's own cron already sends it as Authorization: Bearer <secret>, so the same check works for both:

app/api/cron/sync/route.ts TypeScript
export async function GET(request: Request) {
  const secret = process.env.CRON_SECRET;

  // Refuse anything without the secret, so nobody else can start the job.
  if (!secret || request.headers.get('authorization') !== `Bearer ${secret}`) {
    return new Response('Unauthorized', { status: 401 });
  }

  await syncEverythingOutstanding();

  return Response.json({ ok: true });
}

Return an error status when the work fails. An outside scheduler sees the status code, so a 500 shows up as a failed run and can trigger a retry or an alert, where a 200 with an error in the body looks like success.

2. Point a scheduler at the production URL

Use your production domain, not a preview URL, which may sit behind Vercel's deployment protection. With CronEngine, create a cron job with:

  • URL: https://your-app.com/api/cron/sync
  • Schedule: */5 * * * *, or anything else. It runs in your account's timezone, so 09:00 stays 09:00 after the clocks change.
  • Authentication: Bearer, with the value of CRON_SECRET. It is encrypted when saved and never shown again.

Press Test in the form and you should see your function's 200. From then on every run is kept in the history with its status and timing, paid plans retry a failed run, and you get an email when the job starts failing.

The free plan has 3,000 runs a month, enough for one job every 15 minutes. Every 5 minutes needs 8,640 runs, which the Starter plan covers at €5.99 a month. CronEngine waits 20 seconds for an answer on the free plan and up to 300 on Business, so a job that takes longer should answer first and finish the work afterwards.

Option 3: GitHub Actions

If the code is on GitHub, a scheduled workflow can call the endpoint with curl for free. It works, with one catch: GitHub starts scheduled workflows late under load and sometimes skips them, and the shortest interval is 5 minutes. For a job that has to be on time, see why GitHub Actions cron runs late.

.github/workflows/cron.yml YAML
on:
  schedule:
    - cron: '*/15 * * * *'

jobs:
  call:
    runs-on: ubuntu-latest
    steps:
      - run: >
          curl -fsS --max-time 60
          -H "Authorization: Bearer ${{ secrets.CRON_SECRET }}"
          https://your-app.com/api/cron/sync

Make the job safe to run twice

Whoever calls the endpoint, a run will one day be late, missed or doubled. Vercel says so for its own cron, and every other scheduler has a version of the same problem. Two habits make that harmless:

  • Process what is outstanding, not what is new since one run ago. "Send every invoice that has not been sent" survives a missed run. "Send the invoices from the last 5 minutes" does not.
  • Make each step idempotent. "Mark as paid" can run twice. "Add 10 credits" cannot, unless you record which event already added them.
  • Stop overlapping runs. Take a lock in your database or Redis at the start and skip the run if it is held, or let the scheduler skip a run while the previous one is still going. CronEngine does that by default.

Questions

Why did my deployment fail with "Hobby accounts are limited to daily cron jobs"?

On the Hobby plan, Vercel only accepts cron expressions that run at most once a day. Anything more frequent, such as 0 * * * * or */30 * * * *, makes the deployment fail. Change the expression to a daily one, upgrade to Pro, or remove it from vercel.json and trigger the endpoint from outside.

Why does my Hobby cron job run at a different minute every day?

Vercel spreads Hobby cron jobs across the hour you choose. A job set to 0 8 * * * can start anywhere between 08:00 and 08:59 UTC. On Pro and Enterprise the job starts within the minute you set.

Can I call my cron endpoint from outside Vercel?

Yes. A cron job on Vercel is an ordinary function behind a URL. Anything that can send an HTTP request can call it, as long as it calls the production address and sends the secret your endpoint checks for.

Does Vercel retry a failed cron job?

No. Vercel does not retry a cron invocation that fails, and its docs say delivery is best effort: a run can occasionally be missed or arrive twice. Write the job so that each run picks up whatever is still outstanding.

Which timezone do Vercel cron jobs use?

Always UTC. To run at 09:00 in Brussels you have to convert the time yourself, and change it twice a year for daylight saving time.

Do Vercel cron jobs run on preview deployments?

No. Vercel sends the request to your production deployment URL. A preview deployment never receives cron requests, so test the endpoint by calling it yourself.

Spotted a mistake, or something that behaves differently for you? Tell us and we will update the guide.

Share X LinkedIn Reddit