Ruby Cron Scheduling — A Complete Guide
Ruby scheduling falls into three camps: write to the system crontab (`whenever`), schedule inside a job queue (`sidekiq-cron`, `delayed_job`), or run a dedicated scheduler process (`clockwork`, `rufus-scheduler`). The right pick depends on whether you're on Heroku, what queue you already use, and how much restart resilience you need.
Updated
The landscape
| Gem | What it does | Best for |
|---|---|---|
| whenever | Ruby DSL that writes to the system crontab | Self-hosted servers where you control crontab |
| sidekiq-cron | Cron scheduling for Sidekiq job queue | Rails apps already using Sidekiq |
| clockwork | Standalone Ruby scheduler process | Apps without queue infrastructure |
| rufus-scheduler | In-process scheduler library | Long-running Ruby services |
| resque-scheduler | Cron scheduling for Resque queue | Apps using Resque |
| delayed_job | ActiveRecord-backed job queue with run_at | Simple Rails background jobs |
| good_job | PostgreSQL-backed job queue with cron | Rails apps wanting no extra infra |
| solid_queue | Rails 8 default queue (with cron support) | New Rails apps |
| Heroku Scheduler | Heroku add-on for scheduled dynos | Heroku-hosted apps with simple needs |
The biggest split: system-cron-based (whenever, Heroku Scheduler) vs in-app-scheduler (sidekiq-cron, clockwork, rufus, etc.). System-cron requires shell access on the deploy host; in-app schedulers run inside your Ruby process(es).
whenever — Ruby DSL → crontab
The classic Rails scheduling gem. Writes a Ruby DSL into the host’s crontab.
bundle add whenever
wheneverize .
config/schedule.rb:
set :output, "log/cron.log"
set :environment, "production"
every 5.minutes do
rake "cache:refresh"
end
every :hour do
rake "stats:rollup"
end
every :day, at: "9:00 am" do
runner "DigestMailer.daily.deliver_now"
end
every :weekday, at: "9:00 am" do
command "/usr/local/bin/check-disk-space.sh"
end
every "*/15 * * * *" do
rake "polling:check"
end
After deploy, run:
bundle exec whenever --update-crontab myapp
This writes entries into the user’s crontab. The Ruby DSL compiles to standard cron expressions — every 5.minutes becomes */5 * * * *, every :day, at: "9:00 am" becomes 0 9 * * *, etc.
What whenever is good for:
- Self-hosted servers where you have shell access
- Rails apps deployed via Capistrano (whenever has built-in Capistrano hooks)
- Teams who prefer a Ruby DSL over editing crontab directly
What it isn’t good for:
- Heroku or other platforms that don’t expose user crontabs
- Multi-instance deploys (the crontab is per-host; if you have N app servers, each runs the job N times)
- Jobs that need restart-aware retry — system cron has no retry built in
sidekiq-cron
Adds cron scheduling to Sidekiq. Schedule is stored in Redis; Sidekiq workers pick up scheduled jobs and run them.
bundle add sidekiq-cron
Define jobs as regular Sidekiq workers:
class DailyDigestJob
include Sidekiq::Job
def perform
User.subscribed.find_each(&:send_digest)
end
end
Register the schedule (e.g., in config/initializers/sidekiq.rb):
schedule = {
"daily-digest" => {
"class" => "DailyDigestJob",
"cron" => "0 9 * * MON-FRI",
"queue" => "default",
"args" => [],
},
"cache-refresh" => {
"class" => "CacheRefreshJob",
"cron" => "*/5 * * * *",
},
}
Sidekiq::Cron::Job.load_from_hash(schedule)
Or from a YAML file:
# config/schedule.yml
daily-digest:
class: DailyDigestJob
cron: "0 9 * * MON-FRI"
cache-refresh:
class: CacheRefreshJob
cron: "*/5 * * * *"
# initializer
Sidekiq::Cron::Job.load_from_hash YAML.load_file("config/schedule.yml")
What sidekiq-cron does that whenever doesn’t:
- Single-fire across many workers. sidekiq-cron coordinates via Redis — even with 10 worker dynos, a
*/5 * * * *schedule fires exactly once every 5 minutes. - Sidekiq features apply. Retries, dead-letter queue, monitoring via the Sidekiq web UI — all the things Sidekiq gives you for ad-hoc jobs apply to scheduled jobs.
- Restart-resilient. Schedule lives in Redis, not in any single process.
For Rails apps already using Sidekiq, this is the default answer. If you’re not using Sidekiq yet, consider whether you need a job queue at all — for simple scheduling, whenever or Heroku Scheduler may be lighter.
clockwork
A standalone Ruby scheduler that runs in its own process and dispatches work to wherever you need it.
bundle add clockwork
clock.rb:
require "clockwork"
require_relative "config/environment"
module Clockwork
every(5.minutes, "cache.refresh") { CacheRefreshJob.perform_later }
every(1.hour, "stats.rollup") { StatsJob.perform_later }
every(1.day, "digest", at: "9:00") { DigestJob.perform_later }
end
Run with:
bundle exec clockwork clock.rb
clockwork itself doesn’t run the work — it dispatches to ActiveJob (perform_later), which in turn enqueues to your configured backend (Sidekiq, Solid Queue, etc.). This separation is the appeal: clockwork is “just” a scheduler, the queue is your existing queue.
Heroku users often pair clockwork with worker dynos: one clock process runs clockwork, N worker processes run the queue. clockwork was originally created to solve Heroku-doesn’t-have-crontab without paying for the Scheduler add-on.
The downside: single point of failure. If the clock dyno crashes, scheduling stops. Heroku auto-restarts dynos but there’s a brief gap.
For Rails 7+ apps, sidekiq-cron or solid_queue’s recurring jobs are usually a better fit — they coordinate via the database/Redis so the scheduling itself is highly available.
Rufus::Scheduler
A general-purpose in-process Ruby scheduler. Use it in any long-running Ruby code.
bundle add rufus-scheduler
require "rufus-scheduler"
scheduler = Rufus::Scheduler.new
scheduler.cron "*/5 * * * *" do
# runs every 5 minutes
end
scheduler.every "10s" do
# runs every 10 seconds (sub-minute precision)
end
scheduler.at "2026-12-31 23:59:00" do
# one-off
end
# Keep the process alive
scheduler.join
Rufus is the scheduling engine behind resque-scheduler and sidekiq-scheduler. (sidekiq-cron is a different gem — it parses its cron lines with Fugit, the cron-parsing library that rufus itself depends on, rather than running on rufus.) For non-Rails Ruby services (Sinatra apps, daemons, CLI tools), rufus-scheduler is the simplest way to add scheduling.
Caveats:
- In-process — process restart loses any in-memory state (use the Sequel persistence option for restart-resilient triggers)
- Multi-instance — running rufus in two processes fires every job twice
- Time precision — Rufus has sub-second precision, but real-world drift means it’s not suitable for “exactly at this microsecond” needs
Heroku Scheduler
The managed scheduling add-on for Heroku. No code, no gems — configure schedules in the Heroku dashboard.
heroku addons:create scheduler:standard
heroku addons:open scheduler
In the web UI, add jobs like:
Schedule: Every 10 minutes
Command: rake digest:check
Each scheduled fire starts a fresh one-off dyno, runs the command, and shuts down. Limits:
- Intervals: 10 minutes, hourly, or daily only. No 5-minute, 30-minute, or 2-hour options.
- Daily jobs fire at a specific UTC time. No per-job timezone.
- Best-effort timing. Heroku doesn’t guarantee exact firing times — jobs are expected to run but can occasionally be skipped or run twice.
- Cold dyno startup counts against your hour-budget. Each run spins up a one-off dyno, and that startup time is billed along with the job itself — factor it in for short, frequent jobs.
For Heroku apps with simple needs, Scheduler is the path of least resistance. For anything that needs 5-minute or sub-minute granularity, custom intervals, or per-job timezone — pair Sidekiq + sidekiq-cron on a worker dyno instead.
Choosing the right one
- Rails app with Sidekiq already? →
sidekiq-cron - Rails app, no queue yet, on a self-hosted server? →
whenever(generates crontab) orsolid_queue(Rails 8 default with recurring jobs) - Rails app on Heroku, simple schedule? → Heroku Scheduler add-on
- Rails app on Heroku, needs 5-minute granularity or per-job timezone? →
sidekiq-cron+ Redis + worker dyno - Non-Rails Ruby service? →
rufus-scheduler(in-process) or invoke from system cron - Rails app + ActiveJob, want to keep things in-DB? →
good_job(PostgreSQL) with its built-in cron, orsolid_queuerecurring jobs - Need SLA-grade reliability? → Move scheduling out of Ruby — use Kubernetes CronJob, AWS EventBridge Scheduler, or system cron to invoke a Ruby HTTP endpoint or rake task on a schedule.
For most production Rails apps in 2026, the answer is sidekiq-cron (with Sidekiq) or solid_queue recurring jobs (the Rails 8 default; works on PostgreSQL, MySQL, or SQLite — no Redis needed). Both coordinate properly across multiple dynos, persist the schedule, and integrate with the rest of your queue infrastructure.
Frequently asked questions
Which Ruby scheduling gem should I use?
Does whenever actually run my jobs?
Is sidekiq-cron officially part of Sidekiq?
How does Heroku Scheduler compare to system cron?
What's the difference between clockwork and Rufus::Scheduler?
Related
Every 5 Minutes
`*/5 * * * *` — the most common Ruby scheduled-job interval.
ToolCron Parser
Verify cron expressions before adding them to whenever or sidekiq-cron.
GuideNode.js Cron Jobs
node-cron, agenda, bullmq — the Node equivalents.
GuidePython Cron Scheduling
APScheduler, schedule, Celery Beat — the Python equivalents.
GuideKubernetes CronJob
Run scheduled Ruby work as a K8s CronJob for SLA-grade reliability.