You have work that needs to run on a schedule: a nightly report, an hourly sync, a cleanup every fifteen minutes. The first question is not "cron or Temporal", it's "what happens when a run fails, overlaps, or is missed". The answer decides which tool you want.
What a cron job gives you
A cron job is a schedule and a command. It's the right amount of machinery for a lot of work, and it stays the right amount as long as the command is short, idempotent, and unimportant enough that a missed run is fine.
It breaks in predictable places:
- Overlap. If the 02:00 run is still going at 03:00, the next one starts anyway. Now two copies are writing to the same table.
- Missed runs. If the host is down at 02:00, nothing runs at 02:00. Classic cron doesn't know it missed anything.
- No history. There's no record of which runs happened, how long they took, or why one failed. You add logging and a status table yourself.
- No control. You can't pause a cron job, backfill last week, or change the schedule without touching a crontab and redeploying.
If none of those bite you, stop reading here and use cron. Most scheduled work really is "run this command once a day".
Temporal Cron Jobs vs Temporal Schedules
Temporal has two ways to run workflows on a schedule, and they are not the same thing.
Temporal Cron Jobs are the older mechanism: you pass a cron string when you start a workflow, and the server spawns the next run only after the current one has completed, failed, or timed out. The schedule is a property of that one workflow execution. Temporal's own docs say it plainly: "We recommend using Schedules instead of Cron Jobs."
Temporal Schedules are a first-class object. A Schedule has its own identity, independent of any workflow execution, and it contains instructions for starting workflows at specific times. What you get on top of a cron string:
- A spec that can be a cron expression, a calendar expression, or an interval ("every 30 minutes", optionally with a phase offset).
- Overlap policies:
Skip(the default),BufferOne,BufferAll,CancelOther,TerminateOther, andAllowAll. You decide what happens when a run is due while the previous one is still going. - Pause and unpause without deleting anything. A paused Schedule ignores its spec, but you can still trigger runs manually.
- Backfill: run every action that would have fired over a time range, now.
- Catchup window: how far back to catch up after an outage. The default is one year.
- Jitter, limited actions, and updates to the spec in place.
And because each action starts a workflow, every run is durable: it retries, survives worker restarts, and shows up in the Web UI with its full history.
Which one, in one table
| You need | Cron job | Temporal Schedule |
|---|---|---|
| Run a short command on a schedule | Yes | Overkill |
| Guarantee runs don't overlap | Build it yourself | Overlap policy |
| Catch up after an outage | Manual | Catchup window, backfill |
| A run that is multi-step, long-running, or retries | No | Yes, it's a workflow |
| Pause, resume, change the schedule | Edit crontab, redeploy | Update the Schedule |
| History of every run with logs | Add it yourself | Built in |
| A persistent worker process | No | Yes |
The last row is the honest cost of Temporal: a Schedule needs a worker running to pick up the workflows it starts. If your only recurring work is one nightly command, that's a process you're keeping alive for nothing.
Both, from one config file
Specific has a building block for each side of this table, and they can share code.
A cron block is a scheduled command. In production every execution is a tracked run with its own logs and CPU/memory metrics, you can trigger one on demand with Run now, and there is no worker to keep alive between runs. Overlapping runs are allowed, so keep cron commands idempotent.
A temporal block is a managed workflow engine: a local dev server with specific dev, a Temporal Cloud namespace with specific deploy. Your worker is a regular service, and it registers Schedules through the Temporal SDK like it would anywhere else.
Here they are together, sharing one build:
build "app" {
base = "node"
command = "npm run build"
}
cron "nightly-report" {
build = build.app
command = "node dist/report.js"
schedule = "0 2 * * *"
env = {
DATABASE_URL = postgres.main.url
}
}
temporal "jobs" {}
service "worker" {
build = build.app
command = "node dist/worker.js"
env = {
DATABASE_URL = postgres.main.url
TEMPORAL_ADDRESS = temporal.jobs.url
TEMPORAL_NAMESPACE = temporal.jobs.namespace
TEMPORAL_API_KEY = temporal.jobs.api_key
}
dev {
command = "node --watch src/worker.js"
}
}
postgres "main" {}schedule is a standard 5-field cron expression in UTC, or a macro: @hourly, @daily, @weekly, @monthly, @yearly. The worker gets the three Temporal references and nothing else changes between environments.
Locally, crons don't fire on their schedule during specific dev (that would be surprising while you iterate). Run one by hand instead, with the cron's resolved environment:
specific exec nightly-reportThe Temporal side runs for real locally: the dev server starts with persistent storage, the Web UI is in the local dashboard, and Schedules your worker creates show up there.
A coding agent can write all of this. It adds the block, runs specific check to validate it, and specific dev to try it.
Common questions
Does a `cron` block need a Temporal worker?
No. Each run is a one-off job built from your build. That is the whole reason to prefer it for simple scheduled commands.
How do I create a Temporal Schedule?
Through the SDK client from any process that can reach the engine, usually the worker on startup or a small setup script. In TypeScript that is client.schedule.create(...) with a spec and an action that names the workflow and task queue. The Temporal docs have the Schedules reference for each SDK.
What time zone are schedules in?
Both are UTC by default: the cron block's schedule, and Temporal cron strings. Temporal Schedule specs let you set a time zone explicitly.
Can a Schedule start a workflow that runs for days?
Yes. That's the case Schedules are built for: the workflow is durable, so it can sleep, retry, and wait for signals long after the tick that started it.
Try it
A complete working project with workflows, activities, and a worker: the durable workflows example (live demo, source).
To start in your own project, give your agent this prompt:
Help me get started with Specific by following: https://docs.specific.dev/for-ai/onboardingOr install the CLI yourself:
curl -fsSL https://specific.dev/install.sh | shThen run specific init and ask your agent for a cron, a Temporal Schedule, or both.
For more depth, see the crons guide and the Temporal guide in our docs, how to run Temporal locally, managed Temporal hosting, and our explainer on what durable execution is.