All posts

June 7, 2026 7 min read

How to monitor Sidekiq Cron jobs in Rails

Sidekiq and sidekiq-cron schedule your jobs — they don't tell you when jobs miss, hang, or silently process nothing. Here's how to add external monitoring to every scheduled Sidekiq job in a Rails app.


Sidekiq is the standard background job processor for Rails applications. sidekiq-cron extends it with scheduled jobs, giving you a clean way to define recurring tasks inside your application code rather than managing system crontabs.

But scheduling and monitoring are different problems. sidekiq-cron fires your jobs on schedule. It does not tell you when a job fails to enqueue because the Sidekiq process went down, when a job starts but never completes, or when a job runs successfully but produces no useful output.

For those failure modes, you need external monitoring that watches your jobs from outside the Rails process.


What sidekiq-cron doesn't catch

sidekiq-cron runs a polling thread inside the Sidekiq process. It checks for due jobs every 10 seconds (configurable) and enqueues them into Redis. A Sidekiq worker then picks up and executes the job.

This architecture has three monitoring gaps:

Process failure. If the Sidekiq process crashes or is killed, sidekiq-cron's polling thread stops. No jobs are enqueued. No alerts fire. The jobs simply stop running until the process restarts.

Worker failure. If a job is enqueued but the worker throws an unhandled exception, Sidekiq retries it according to its retry policy. After exhausting retries, the job moves to the dead queue. Sidekiq Web shows this, but no external alert fires by default.

Silent failure. If a job runs to completion but processes zero records, sends no emails, or produces empty output, Sidekiq sees a successful job. No alert fires, because from Sidekiq's perspective, everything worked.


The monitoring pattern

External monitoring for Sidekiq Cron jobs uses the same dead man's switch model as any cron monitoring: your job sends HTTP pings when it starts and when it finishes. An external service tracks whether pings arrive on schedule.

Add a ping helper to your ApplicationJob or a dedicated concern:

# app/jobs/concerns/crontify_monitoring.rb
module CrontifyMonitoring
  extend ActiveSupport::Concern

  private

  def ping(monitor_id, event, payload = {})
    uri = URI("https://api.crontify.com/api/v1/ping/#{monitor_id}/#{event}")
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    http.open_timeout = 5
    http.read_timeout = 5

    request = Net::HTTP::Post.new(uri)
    request["X-API-Key"] = ENV["CRONTIFY_API_KEY"]
    request["Content-Type"] = "application/json"
    request.body = payload.to_json unless payload.empty?

    http.request(request)
  rescue StandardError
    # Monitoring failure must never kill the job
  end

  def with_crontify(monitor_id)
    ping(monitor_id, "start")
    meta = {}
    yield meta
    ping(monitor_id, "success", { meta: meta }.compact)
  rescue => e
    ping(monitor_id, "fail", {
      message: e.message,
      log: e.backtrace.first(20).join("\n")
    })
    raise
  end
end

Include it in any scheduled job:

# app/jobs/nightly_sync_job.rb
class NightlySyncJob < ApplicationJob
  include CrontifyMonitoring

  MONITOR_ID = "your-monitor-id"

  def perform
    with_crontify(MONITOR_ID) do |meta|
      result = SyncService.run

      meta[:records_synced] = result.count
      meta[:duration_ms] = result.duration_ms
      meta[:errors] = result.errors
    end
  end
end

Schedule it in your sidekiq.yml or initializer as normal:

# config/sidekiq.yml
:schedule:
  nightly_sync:
    cron: "0 2 * * *"
    class: NightlySyncJob

The with_crontify block sends a start ping before executing, a success ping with metadata when the block completes, and a fail ping with the error message and backtrace if an exception is raised.


Adding silent failure detection

The meta hash you populate inside the block is sent with the success ping and stored against the run record in Crontify. You then define alert rules in the dashboard:

  • records_synced eq 0 → fire alert
  • errors gt 0 → fire alert
  • duration_ms gt 300000 → fire alert (job took more than 5 minutes)

A job that completes without error but syncs zero records will still trigger an alert. Without this, a silent failure in a Rails background job is invisible — Sidekiq marks it successful, the scheduler re-enqueues it next cycle, and the damage accumulates.


Handling jobs that run on multiple dynos or processes

If you run multiple Sidekiq processes (common on Heroku or Kubernetes), sidekiq-cron ensures only one process enqueues each scheduled job — it uses a Redis lock to prevent duplicates. This means you will still only get one start ping per scheduled execution, which is the correct behaviour.

If a job is enqueued multiple times due to a race condition (rare but possible), Crontify will detect the overlap and fire an alert. You can also add an around_perform callback using Redis locks to prevent concurrent execution entirely:

around_perform do |job, block|
  lock_key = "job_lock:#{job.class.name}"
  acquired = $redis.set(lock_key, "1", nx: true, ex: 3600)
  if acquired
    begin
      block.call
    ensure
      $redis.del(lock_key)
    end
  else
    Rails.logger.warn "#{job.class.name} already running, skipping"
  end
end

What you get from external monitoring

After instrumenting your Sidekiq Cron jobs:

  • Missed runs alert you when no start ping arrives within the grace period — catches Sidekiq process failures, Redis outages, and deployment interruptions that leave the scheduler stopped.
  • Hung jobs alert you when a job starts but never completes — catches deadlocks and jobs stuck waiting on external services.
  • Silent failures alert you when a job completes but its output metrics don't meet your defined thresholds.
  • Recovery alerts fire automatically when a previously failing job returns to healthy.

Crontify is free for up to 5 monitors — no credit card required.


Start monitoring your scheduled jobs

Free plan includes 5 monitors. No credit card required. Up and running in under 5 minutes.

Get started free →