Cron expressions are the universal language for scheduling recurring tasks. But when you move from traditional crontab to serverless platforms, each platform has its own quirks, limitations, and syntax differences. This guide covers everything you need to schedule jobs on the three most popular serverless platforms.
Cron Expression Basics (60-Second Refresher)
A standard cron expression has 5 fields:
āāāāāāāāāāāāāā minute (0-59)
ā āāāāāāāāāāāāāā hour (0-23)
ā ā āāāāāāāāāāāāāā day of month (1-31)
ā ā ā āāāāāāāāāāāāāā month (1-12)
ā ā ā ā āāāāāāāāāāāāāā day of week (0-6, Sun=0)
ā ā ā ā ā
* * * * *Common special characters:
*ā any value*/5ā every 5 units (step)1,3,5ā specific values (list)1-5ā range of values
Test your cron expressions with our Cron Parser tool ā
Platform 1: GitHub Actions
GitHub Actions uses the schedule event with standard 5-field cron syntax. Scheduled workflows run on the default branch only.
Configuration
# .github/workflows/scheduled.yml
name: Scheduled Job
on:
schedule:
- cron: '30 5 * * 1-5' # Weekdays at 5:30 AM UTC
- cron: '0 12 1 * *' # 1st of every month at noon UTC
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Running scheduled task"Key Limitations
| Aspect | Details |
|---|---|
| Timezone | UTC only ā no timezone configuration |
| Minimum interval | Every 5 minutes (*/5 * * * *) |
| Accuracy | Can be delayed 5-15 minutes during peak load |
| Branch | Runs on default branch only |
| Disable condition | Auto-disabled after 60 days of repo inactivity |
Common Schedules for GitHub Actions
# Every 5 minutes
- cron: '*/5 * * * *'
# Every hour at minute 0
- cron: '0 * * * *'
# Daily at midnight UTC
- cron: '0 0 * * *'
# Daily at 9 AM Eastern (UTC-5, no DST)
- cron: '0 14 * * *'
# Weekdays at 8 AM UTC
- cron: '0 8 * * 1-5'
# Every Monday and Thursday at 6 PM UTC
- cron: '0 18 * * 1,4'
# First day of every month at midnight
- cron: '0 0 1 * *'Tip: GitHub Actions scheduled workflows may not fire exactly on time. If you need precision, use a webhook-based trigger from a more reliable scheduler.
Platform 2: Vercel Cron Jobs
Vercel Cron Jobs trigger serverless functions on a schedule. Configure them in vercel.json.
Configuration
// vercel.json
{
"crons": [
{
"path": "/api/daily-report",
"schedule": "0 8 * * *"
},
{
"path": "/api/cleanup",
"schedule": "0 0 * * 0"
}
]
}The API Route
// app/api/daily-report/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
// Your scheduled logic here
await sendDailyReport();
return NextResponse.json({ ok: true });
}
// Vercel recommends setting maxDuration for long tasks
export const maxDuration = 60; // secondsKey Limitations
| Plan | Min Interval | Max Cron Jobs | Execution Limit |
|---|---|---|---|
| Hobby (free) | Once per day | 2 | 10s |
| Pro | Once per hour | 40 | 60s (default) |
| Enterprise | Once per minute | 100 | 900s |
Important: Vercel Cron Jobs use UTC timezone. The Hobby plan's once-per-day limit means expressions like */5 * * * * will not work ā they'll only fire once daily.
Platform 3: Cloudflare Workers (Cron Triggers)
Cloudflare Workers Cron Triggers are configured in wrangler.toml and run at the edge globally.
Configuration
# wrangler.toml
name = "my-worker"
main = "src/index.ts"
[triggers]
crons = [
"*/5 * * * *", # Every 5 minutes
"0 12 * * 1-5", # Weekdays at noon UTC
"0 0 1 * *" # First of every month
]Worker Code
// src/index.ts
export default {
async scheduled(
controller: ScheduledController,
env: Env,
ctx: ExecutionContext
) {
// controller.cron contains the cron pattern that triggered
switch (controller.cron) {
case '*/5 * * * *':
await doHealthCheck(env);
break;
case '0 12 * * 1-5':
await sendReport(env);
break;
}
},
};Key Limitations
| Aspect | Details |
|---|---|
| Timezone | UTC only |
| Minimum interval | Once per minute |
| Max cron triggers | 3 per Worker (free), more on paid plans |
| CPU time | 10ms (free) / 30s (paid) per invocation |
| Accuracy | Very reliable ā runs at the edge |
Cross-Platform Comparison
| Feature | GitHub Actions | Vercel Cron | CF Workers |
|---|---|---|---|
| Timezone | UTC | UTC | UTC |
| Min interval (free) | 5 min | 1/day | 1 min |
| Min interval (paid) | 5 min | 1/min | 1 min |
| Reliability | Medium (can delay) | High | Very high |
| Max execution time | 6 hours | 10s-900s | 10ms-30s |
| Config location | .github/workflows/ | vercel.json | wrangler.toml |
| Trigger handler | Workflow YAML | API route | scheduled() event |
Common Timezone Pitfall
All three platforms use UTC exclusively. This is the most common source of bugs when setting up cron schedules. Here's a quick UTC conversion table:
| Local Time | UTC Offset | Cron (9 AM local) |
|---|---|---|
| US Eastern (EST) | UTC-5 | 0 14 * * * |
| US Pacific (PST) | UTC-8 | 0 17 * * * |
| Central Europe (CET) | UTC+1 | 0 8 * * * |
| China (CST) | UTC+8 | 0 1 * * * |
| Japan (JST) | UTC+9 | 0 0 * * * |
Remember: Daylight Saving Time shifts UTC offsets. EST (UTC-5) becomes EDT (UTC-4) in summer. Your cron jobs won't adjust automatically.
10 Copy-Paste Cron Expressions
Here are the most commonly needed schedules, ready to use on any platform:
# Every 5 minutes
*/5 * * * *
# Every hour at :00
0 * * * *
# Every day at midnight UTC
0 0 * * *
# Every day at noon UTC
0 12 * * *
# Weekdays (Mon-Fri) at 9 AM UTC
0 9 * * 1-5
# Every Monday at 8 AM UTC
0 8 * * 1
# Every 6 hours
0 */6 * * *
# First day of every month at midnight
0 0 1 * *
# Every 15 minutes during business hours (8-17 UTC)
*/15 8-17 * * 1-5
# Last day of month at 11 PM UTC (approximate)
0 23 28-31 * *Validate any cron expression with our Cron Parser ā
Frequently Asked Questions
What timezone do GitHub Actions cron schedules use?
GitHub Actions cron schedules always use UTC. There is no option to change the timezone. If you need a job to run at 9 AM Eastern Time, you must convert that to UTC (1 PM or 2 PM depending on daylight saving time) and use the corresponding cron expression.
What is the minimum interval for Vercel Cron Jobs?
On the Vercel Hobby (free) plan, the minimum interval is once per day. On the Pro plan, the minimum is once per hour. On the Enterprise plan, the minimum is once per minute. Vercel uses standard 5-field cron expressions.
Can Cloudflare Workers Cron Triggers run every second?
No. Cloudflare Workers Cron Triggers use standard cron expressions with a minimum granularity of one minute. For sub-minute scheduling, you would need to use Durable Objects with the alarm() API.