Scheduled tasks are a fact of life in any non-trivial Rails app—whether it’s sending out nightly digests, cleaning up stale data, or syncing with external APIs. If you’re using Heroku Scheduler to kick off these jobs, you’ve likely run into its limitations: fixed time windows, separate logging, and the hassle of managing multiple “one-off” dynos. In this article, I’ll show you how to consolidate your cron jobs into Sidekiq Cron, giving you unified monitoring, richer scheduling options, and the performance benefits of Sidekiq’s threaded workers.

Why Heroku Scheduler Isn’t Enough

Heroku Scheduler is great for a quick setup:

# In your Heroku Dashboard
# Schedule "bundle exec rake some:task" daily at 02:00 UTC

But after a while, you’ll notice:

  • Limited intervals: You can only choose hourly, daily, or custom (every X minutes), without second-level precision.
  • Separate dynos: Each job spins up a one-off dyno, leading to cold-start delays and inconsistent performance.
  • Disconnected logs: Your scheduled rake output lands in its own log stream, making it harder to correlate with Sidekiq job failures or retries.

Wouldn’t it be nicer to have all your background and scheduled work living under one umbrella?

Meet Sidekiq Cron

Sidekiq Cron is a plugin that lets you define recurring jobs in Sidekiq using cron syntax. You get:

  • Centralized dashboard: See both your real-time and scheduled jobs in one UI.
  • Flexible schedules: Full cron support (every minute, specific weekdays, even “the last Friday of the month”).
  • In-process execution: No more cold starts—jobs run right alongside your regular Sidekiq workers.

Installing

Add it to your Gemfile:

# Gemfile
gem 'sidekiq'
gem 'sidekiq-cron'

Then bundle and restart Sidekiq:

bundle install
bundle exec sidekiq

Defining Your Cron Jobs

You can configure schedules in code or YAML. Here’s an initializer approach:

# config/initializers/sidekiq_cron.rb
require 'sidekiq'
require 'sidekiq/cron/job'

schedule = {
  "daily_cleanup" => {
    "cron"  => "0 3 * * *",      # every day at 03:00
    "class" => "DailyCleanupJob"
  },
  "hourly_sync"  => {
    "cron"  => "*/15 * * * *",   # every 15 minutes
    "class" => "ExternalApiSyncJob"
  }
}

Sidekiq::Cron::Job.load_from_hash(schedule)

Or use a YAML file for easier editing:

# config/schedule.yml
daily_cleanup:
  cron: "0 3 * * *"
  class: "DailyCleanupJob"

hourly_sync:
  cron: "*/15 * * * *"
  class: "ExternalApiSyncJob"

Then load it in your initializer:

Sidekiq::Cron::Job.load_from_array(YAML.load_file(Rails.root.join('config/schedule.yml')))

Benefits of Migrating

Unified Monitoring

No more bouncing between Heroku logs and Sidekiq UI. You can track failures, retries, and timing graphs all in one place.

Performance Gains

Scheduled jobs run in your existing Sidekiq process—no extra dynos, no cold starts, and no duplicated environment boot time.

Granular Scheduling

Need a job every two hours on weekdays, skip holidays, or run at the end of the month? Cron syntax has you covered.

Dynamic Control

Turn jobs on/off or update schedules at runtime via the Sidekiq Web UI. No code deploy required for minor tweaks.

Putting It in Your Procfile

If you use Foreman or Heroku’s Procfile, replace separate scheduler entries with:

web:    bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq -C config/sidekiq.yml

And remove your Heroku Scheduler jobs. All recurring work will flow through your worker dyno.

Conclusion

Migrating from Heroku Scheduler to Sidekiq Cron streamlines your Rails background architecture. You gain powerful cron flexibility, better resource utilization, and a single pane of glass for all asynchronous work. Next time you’re sketching out a new rake task for Heroku Scheduler, consider folding it into Sidekiq Cron instead—you’ll thank yourself later.

Happy queueing! 🚀