intermediate 11 min read

How to Set Up Cron Jobs in Ruby on Rails

Learn how to schedule cron jobs in Ruby on Rails using the whenever gem, sidekiq-cron, and custom rake tasks. Step-by-step guide with deployment tips.

Prerequisites

  • Ruby 3.1+
  • Rails 7+
  • Bundler

Cron Options in Rails

Rails offers several approaches for scheduled tasks:

  • whenever gem — DSL that generates crontab entries from Ruby code
  • sidekiq-cron — Cron scheduling on top of Sidekiq (requires Redis)
  • solid_queue — Rails 8+ built-in job backend with recurring tasks
  • Custom rake tasks + system cron — Simple, no dependencies

For most Rails apps, the whenever gem is the easiest starting point. For apps already using Sidekiq, sidekiq-cron integrates naturally.

Quick Start with the whenever Gem

Install:

ruby
# Gemfile
gem 'whenever', require: false
bash
bundle install
wheneverize .

This creates config/schedule.rb.

Define your schedule:

ruby
# config/schedule.rb
every 5.minutes do
  runner "CleanupService.run"
end

every 1.day, at: '4:30 am' do
  rake "reports:daily"
end

every :monday, at: '9:00 am' do
  runner "WeeklyDigest.send_all"
end

Apply to crontab:

bash
whenever --update-crontab
whenever -l  # List generated crontab entries

Sidekiq-Cron for Background Jobs

If you're already using Sidekiq, sidekiq-cron adds recurring job support:

ruby
# Gemfile
gem 'sidekiq-cron'

Define recurring jobs (config/initializers/sidekiq_cron.rb):

ruby
Sidekiq::Cron::Job.load_from_hash(
  'cleanup' => {
    'cron' => '*/5 * * * *',
    'class' => 'CleanupWorker',
  },
  'daily_report' => {
    'cron' => '0 9 * * *',
    'class' => 'DailyReportWorker',
  }
)
ruby
# app/workers/cleanup_worker.rb
class CleanupWorker
  include Sidekiq::Worker

  def perform
    OldRecord.where('created_at < ?', 30.days.ago).delete_all
  end
end

Sidekiq-cron provides a web UI at /sidekiq/cron to monitor jobs.

Custom Rake Tasks

The simplest approach — write a rake task and schedule it via system cron:

ruby
# lib/tasks/maintenance.rake
namespace :maintenance do
  desc "Clean up expired sessions"
  task cleanup: :environment do
    deleted = Session.expired.delete_all
    puts "Deleted #{deleted} expired sessions"
  end
end
bash
# Add to crontab
crontab -e
0 2 * * * cd /opt/myapp && RAILS_ENV=production bundle exec rake maintenance:cleanup >> /var/log/cron.log 2>&1

Always set RAILS_ENV=production and use bundle exec to ensure the correct Ruby and gems are used.

Common Cron Expressions

Standard 5-field expressions used with system cron or sidekiq-cron:

  • */5 * * * * — Every 5 minutes
  • 0 * * * * — Every hour
  • 0 0 * * * — Daily at midnight
  • 0 9 * * 1-5 — Weekdays at 9 AM
  • 0 0 1 * * — First day of every month

With the whenever gem, use Ruby syntax instead:

ruby
every 5.minutes { runner "Task.run" }
every 1.hour { rake "process:queue" }
every 1.day, at: '12:00 am' { runner "Cleanup.run" }
every :weekday, at: '9:00 am' { runner "Report.send" }

Every weekday at 9:00 AM

Next runs (UTC):

Mon, May 18, 2026 09:00

Tue, May 19, 2026 09:00

Wed, May 20, 2026 09:00

Deployment with Capistrano

The whenever gem integrates with Capistrano:

ruby
# Gemfile
gem 'whenever', require: false
ruby
# config/deploy.rb
set :whenever_roles, -> { :app }

# This automatically updates crontab on deploy
require 'whenever/capistrano'

For Docker deployments, you'll need to run the cron daemon in your container or use a sidecar approach. See our Docker Cron Setup Guide for details.

Debugging

Task not running?

  • Check crontab is installed: crontab -l
  • Verify the Ruby/Rails environment: set RAILS_ENV and use full paths
  • Check cron logs: grep CRON /var/log/syslog

whenever preview:

bash
whenever  # Shows the crontab entries that would be generated (dry run)

sidekiq-cron debugging:

ruby
# Rails console
Sidekiq::Cron::Job.all.each do |job|
  puts "#{job.name}: #{job.cron} - #{job.status}"
end

Production Tips

Logging: Always log to a file and use Rails logger:

ruby
# In your scheduled task
Rails.logger.info "Starting daily cleanup"

Error tracking: Wrap tasks with error reporting:

ruby
task cleanup: :environment do
  Cleanup.run
rescue => e
  Sentry.capture_exception(e)
  raise
end

Health checks: Ping a monitoring service after successful runs:

ruby
every 1.hour do
  runner "ProcessQueue.run; Net::HTTP.get(URI('https://hc-ping.com/uuid'))"
end

Frequently Asked Questions