Cron and background job queues both run work outside the request and response cycle, but they solve different problems. Cron answers "run this at a specific time" using the operating system's or platform's scheduler and nothing else, with no queue, no worker pool, and no retry logic beyond what you build yourself. A job queue answers "run this reliably, whenever it's triggered, with retries and visibility" by decoupling the trigger from execution across a pool of workers. Picking between them comes down to whether your work is purely time-based and single-instance, or needs retries, concurrency control, and multiple triggers.
What changed in 2026
- Kubernetes CronJob became the default even for simple schedules, replacing host-level crontab for teams already running on Kubernetes. It gets you retries and history for free, but still runs each job as a single pod, so overlap protection is still your job.
- Managed schedulers absorbed most new scheduled-job traffic. AWS EventBridge Scheduler and GCP Cloud Scheduler now handle the "fire an event on a schedule" case without a server holding a crontab at all.
- Workflow engines blurred the line further. Temporal and similar durable-execution systems now schedule work and guarantee retries and idempotent step execution, covering both cron's simplicity and a queue's reliability in one system.
- Silent duplicate cron runs remained one of the most common scaling incidents, as teams moved from one instance to several without adding a lock. This failure mode has not gone away just because tooling improved.
Comparing the models
| Factor |
Cron |
Background job queue |
| Trigger |
Time only |
Schedule, event, API call, webhook |
| Retries on failure |
None built-in |
Built-in, configurable backoff |
| Multiple workers |
Runs on every instance unless guarded |
Designed for it; jobs claimed once |
| Visibility |
Logs only, usually |
Dashboard, job status, history |
| Setup cost |
Minimal |
Needs a broker or queue and workers |
| Failure isolation |
One failed job can block the next tick |
One failed job doesn't block others |
| Best for |
Simple, single-instance, time-only tasks |
Anything needing retries, scale, or non-time triggers |
How to choose
- Single server, purely time-based, low stakes if it occasionally fails silently? Plain cron or a managed scheduler is enough; do not add a queue you don't need.
- Multiple replicas need to run the same scheduled task exactly once? Either use a scheduler that guarantees single execution, such as a single-replica Kubernetes CronJob or a managed scheduler, or add a distributed lock around the job body.
- The work needs retries with backoff, or visibility into failures? Move it to a job queue; cron gives you none of that for free.
- The trigger is an event, not a clock, such as "when a user signs up" or "when a file uploads"? That's not a scheduling problem at all; enqueue directly from the event and skip cron entirely.
- You need both a schedule and reliable retries or state, like nightly reconciliation jobs or billing runs? A workflow engine such as Temporal, or a queue with a cron-trigger plugin, covers both without gluing two systems together yourself.
Common mistakes
Running cron on every replica without a lock. The classic incident: three app servers, one crontab each, and one job runs three times at 2am, tripling a batch email send or corrupting a shared report.
Using cron for anything that needs a retry. A cron job that fails simply doesn't run again until the next tick, hours later. If the task matters, it needs a queue with a retry policy, not a bare cron entry.
Building a home-grown queue on top of a database table. It's a common first step, but polling a table for unprocessed rows re-invents locking, backoff, and dead-letter handling that mature job queues already solved, and it is worth the switch once volume grows past a trivial scale.
No alerting on missed or failed runs. A cron job that silently stops running, because of a bad deploy, config drift, or a replaced host, can go unnoticed for weeks without a heartbeat check or a missed-run alert.
FAQ
Can I just use cron and add my own retry logic?
You can, but you'll end up rebuilding a subset of what a job queue already provides, including backoff and dead-letter handling. It's usually less work to adopt a lightweight queue once you need retries.
Does Kubernetes CronJob solve the multiple-replicas problem?
It solves scheduling one pod to run the job, but if you scale the CronJob's job spec to multiple parallel pods, or run more than one CronJob resource pointing at the same task, you still need your own overlap protection.
When is cron clearly the right answer?
Single-instance, purely time-triggered, low-consequence-on-failure tasks, such as log rotation, cache warming, or a nightly cleanup script where a missed run is a minor inconvenience rather than an incident.
How do I prevent two cron runs from overlapping if the job runs longer than the interval?
Use a lock, such as an advisory lock in Postgres, a Redis SET NX, or a dedicated distributed lock, that the job acquires before running and releases after. See distributed locks explained.
Where to go next