Pulse
7IT Solutions
Automation

Scheduled Jobs and Background Work on Vercel: A Technical Guide

Lior Aharonov Lior Aharonov 7 min read Updated 2026-06-22

Serverless is built for handling requests: someone hits a URL, a function wakes up, responds, and goes back to sleep. But real applications also need work that happens away from any request. A nightly report has to generate itself, an abandoned cart needs a reminder, data has to sync on a schedule, a subscription has to be charged at the right time. None of that is triggered by a user clicking a button, and that is where a lot of otherwise solid Vercel apps get stuck.

The good news is that Vercel has the pieces for this, and they are not complicated once you understand the one constraint that shapes the whole design. This guide covers how to run scheduled and background work properly on Vercel, with code you can adapt.

The constraint that shapes everything: functions are short-lived

Serverless functions have a maximum execution time. They are meant to start, do a focused piece of work, and finish quickly, which is perfect for a request and a problem for a job that needs ten minutes to churn through ten thousand records. If you try to do all of that inline, the function hits its limit and gets killed partway through, leaving the work half-done.

So the governing principle is this: keep every individual run short, and break long work into many small units. Almost every pattern below is a way to honor that one rule. Once you design around it instead of fighting it, the rest falls into place.

Scheduled work: Vercel Cron

For anything that runs on a clock, Vercel Cron is the native tool. You declare schedules in vercel.json, each pointing at a route, and Vercel calls that route on standard cron syntax.

{ "crons": [{ "path": "/api/cron/nightly", "schedule": "0 3 * * *" }] }

Because the path is a normal URL, protect it so only the scheduler can run it, by checking the bearer token Vercel sends from your CRON_SECRET. And keep the handler itself tiny: rather than doing the heavy work in the cron run, use it to find what needs doing and hand each piece off, so no single invocation risks the time limit.

// the cron route stays small: it fans work out instead of doing it all inline
export async function GET(req: Request) {
  if (req.headers.get("authorization") !== `Bearer ${process.env.CRON_SECRET}`) {
    return new Response("unauthorized", { status: 401 });
  }
  const ids = await db.pendingReportIds();
  await Promise.all(ids.map((id) => queue.enqueue({ id })));  // one short job each
  return new Response("ok");
}

Long or heavy work: offload to a queue

When a unit of work might exceed the function limit, or simply must not be lost if it fails, a queue is the answer. Instead of processing inline, you publish a message, and the queue calls back a worker route to handle it, one short job at a time, retrying automatically if it fails. A managed option like Upstash QStash fits Vercel well because it is just HTTP: you publish to a URL, and it delivers to your endpoint.

This turns one impossible ten-minute job into a thousand ten-millisecond ones, each comfortably inside the limit, each retried on its own if something goes wrong. The cron run becomes a dispatcher and the queue becomes the engine, which is exactly the shape serverless wants.

Do not let jobs overlap: locking

A subtle bug bites scheduled work: a job that runs every few minutes can start a new run before the previous one finished, and now two copies are processing the same records, sending the same email twice or double-charging a card. The fix is a lock that lets only one run proceed at a time.

A single atomic operation does it. Set a key only if it does not already exist, with a time-to-live so the lock releases itself even if a run dies mid-way and never cleans up.

// only one runner at a time; the TTL frees the lock if a run dies
const ok = await redis.set("lock:nightly", "1", { nx: true, ex: 600 });
if (!ok) return;          // another run holds the lock, skip this tick
try {
  await doWork();
} finally {
  await redis.del("lock:nightly");
}

Make jobs idempotent and resumable

Background jobs get retried and sometimes killed partway through, so design each one to be safe to run again. That means idempotent writes, deduping on stable ids and using upserts, so a re-run does not duplicate what a previous attempt already did.

For larger jobs, process in batches and checkpoint your progress, recording how far you got, so a retry resumes from the last checkpoint instead of starting over or doubling up. The combination, small idempotent units plus a saved cursor, is what makes a long job survive being interrupted, which on serverless it eventually will be.

Observe: logging, alerting, and the job that never ran

Background work is invisible by nature, which is its real danger. A failing API throws an error someone sees, but a cron job that silently stops running produces nothing at all, and you only notice when the report that should have arrived did not. So watch for absence as much as failure.

Log each run with its start, end, duration, and outcome. Alert when a job fails repeatedly, when a backlog grows, and, crucially, when an expected run did not happen at all, a simple heartbeat check that flags "the nightly job has not succeeded in 25 hours" catches the failures that produce no error. Route work that fails permanently to a dead-letter store so it waits for a human instead of retrying forever.

A background-jobs checklist

  • Assume functions are short-lived, keep every run small and break long work into units.
  • Use Vercel Cron for schedules, guard the route with CRON_SECRET, and keep the handler a dispatcher.
  • Offload heavy or must-not-fail work to a queue with automatic retries.
  • Use a lock with a TTL so scheduled runs never overlap.
  • Make jobs idempotent, process in batches, and checkpoint progress so retries resume.
  • Log start, end, and duration, and alert on failures, backlog, and missed runs.
  • Dead-letter anything that fails permanently so it is inspected, not lost.

FAQ

How do I run a cron job on Vercel?

Add a crons entry to vercel.json with a path and a standard cron schedule, and Vercel will call that route at the set times. Protect the route by verifying the bearer token Vercel sends from your CRON_SECRET environment variable, since the path is otherwise a public URL. Keep the handler short, ideally using it to dispatch work to a queue rather than doing long processing inline.

My job takes longer than the function limit, what do I do?

Stop trying to do it in one run. Break the work into many small units and process them through a queue, where each message is a short job that fits inside the function limit and retries on its own if it fails. The scheduled run becomes a dispatcher that enqueues the pieces, and the queue works through them. Batching with saved checkpoints lets even very large jobs complete across many short invocations.

How do I stop a scheduled job from running twice?

Use a lock. Before a run starts, atomically set a key only if it does not already exist, with a time-to-live, and skip the run if the key is already held. The TTL ensures the lock releases itself if a run dies without cleaning up, so you never deadlock. This prevents a new scheduled run from overlapping one that is still in progress and processing the same records twice.

How do I know if a scheduled job failed or never ran?

Build for absence, not just errors. Log every run's outcome and add a heartbeat check that alerts when an expected run has not succeeded within its window, for example flagging a nightly job that has not completed in 25 hours. A job that silently stops produces no error to catch, so the only way to notice is to watch for the missing success and alert on it, alongside normal failure and backlog alerts.

Do I need a queue, or is cron enough?

Cron alone is fine when the scheduled work is small and finishes well within the function limit. Add a queue once the work is long, needs reliable retries, or must fan out into many pieces, because the queue gives you short units, automatic redelivery on failure, and a dead-letter record. A common, robust setup is cron to trigger on schedule plus a queue to actually do the heavy lifting.

If your app needs reports, reminders, syncs, or billing that run on their own, tell me what has to happen and when and I will map out a background-job setup on Vercel that holds up under load.

Want a hand applying this?

Tell me where your business is stuck and I will give you a straight, useful read, no pitch.

Go deeper

Custom Software

Leaving Bolt.new for Vercel: Own Your Project and Stop Paying to Iterate

Why teams move their Bolt.new project onto Vercel, what they gain in ownership and control, the features you can finally build once you are outside the builder, and how developing with Claude and Git directly cuts the token cost of every future change.

Read →
Custom Software

How to Migrate a Website from Bolt.new to Vercel: A Simple, Safe Guide

A clear, step-by-step guide to moving a website from Bolt.new to Vercel with no lost work and no downtime, from exporting your code to GitHub through to your custom domain, plus the calmer Claude and Git workflow that takes over afterward.

Read →
Automation

API Integrations: Why Connecting Your Stack Beats Copy-Paste

Why manually moving data between your business tools is costing more than you think, and how API integrations make your software work as one system.

Read →
Shopify

Real Multi-Channel Inventory Sync: Why Shopify Connectors Keep Breaking

Multi-location stock, kits and bundles, and keeping Shopify in step with a 3PL or ERP is where connector apps quietly fail and oversells start. Here is why generic connectors break, what a custom sync layer does differently, and how we build it as your single source of truth.

Read →
Custom Software

Smart Scraping Bots That Fake an iPhone: How Cloudflare and Vercel Fight Back

Modern scrapers spoof a real iPhone user agent while arriving from a datacenter ASN halfway across the world. Here is how that trick works, why blocking by user agent fails, and the Cloudflare and Vercel bot-protection features that actually stop them without turning away real customers.

Read →
Automation

The Hidden Cost of Manual Workflows (and What to Automate First)

How to spot the manual work quietly draining your team, and a simple way to pick the first automation that pays for itself for a US business.

Read →