Cron Schedule Builder for Rails

Set the five fields, read the schedule back in plain English, and take away the three things a Rails app actually needs - the crontab line, the config/schedule.rb block, and the recurring job entry.

All tools

Your schedule

Every 5 minutes, every day.

Use this schedule in a Rails app

crontab

*/5 * * * * cd /var/www/app/current && bin/rails runner 'CleanupJob.perform_later'

config/schedule.rb (whenever gem)

every 5.minutes do
runner "CleanupJob.perform_later"

The whenever gem also takes a five field line verbatim, so anything the Ruby sugar will not express can be written out in full.

config/recurring.yml (Solid Queue and GoodJob)

production:
  cleanup_job:
    class: CleanupJob
    schedule: "*/5 * * * *"

# GoodJob: config.good_job.cron = { cleanup_job: { cron: "*/5 * * * *", class: "CleanupJob" } }

Field by field

0-59

0-23

1-31

1-12 or JAN-DEC

0-6 or SUN-SAT

Next five runs

    Everything runs in your browser. Nothing is uploaded.

    The five fields a Rails schedule is built from

    A crontab line is five fields separated by spaces, always in the same order: minute, hour, day of the month, month, day of the week. Minute runs 0 to 59, hour 0 to 23, day of the month 1 to 31, month 1 to 12, and day of the week 0 to 6 with Sunday also written as 7. A star stands for every value the field can hold, a comma lists values, a hyphen gives a range and a slash gives a step, so `*/15` in the minute field is a quarter past, half past, quarter to and on the hour. Month and day of the week also accept three letter names, so JAN and MON read the same as 1 and 1. Every scheduler in this list - crontab, the whenever gem, Solid Queue, GoodJob - agrees on those five fields, which is why one line is enough to describe a schedule to all of them.

    What the whenever gem does with these fields

    The whenever gem does not replace cron, it writes for it. You describe the schedule in Ruby in config/schedule.rb, and `whenever --update-crontab` compiles that file into the crontab entries for the deploying user, each one carrying the path, the bundler context and the environment that a hand written line so often forgets. The sugar covers the shapes a Rails app reaches for most - an interval such as `every 5.minutes`, a daily run with one or several times, a single weekday, the working week - and falls back to the raw five field line for anything else. The block above is generated from whichever of those your schedule fits.

    Recurring jobs without a crontab: Solid Queue and GoodJob

    Solid Queue ships with Rails 8 and reads its schedule from config/recurring.yml, keyed by environment, with a class or a command and a schedule string per task. GoodJob does the same through config.good_job.cron in an initializer, as a hash of job definitions. Both parse the schedule with fugit, which understands the five fields exactly as written here and also accepts a timezone after them, so a nightly job can be pinned to a real place rather than to whatever clock the box keeps. The practical difference from crontab is where a failure lands - a recurring task that raises is a failed job in the queue, with its backtrace and its retries, instead of a line of mail nobody reads.

    Questions Rails developers ask about cron

    A weekday, a time and a cron field mean the same thing in both, but you do not write the five fields in schedule.rb unless you want to. The gem takes Ruby - `every 1.day, at: '03:00'` or `every :weekday, at: '09:00'` - and compiles it to the crontab line above, which is what `whenever --update-crontab` writes to the server. Anything the sugar cannot express you can still hand it verbatim, as `every '0 3 1 * *' do`, so nothing about five field cron is off limits.

    Nothing in a crontab line carries a timezone, so cron fires on whatever clock the machine keeps - usually UTC on a deployed server. Rails only applies Time.zone once the process is running, which is well after cron has decided it is time, so a job written for 09:00 local fires at 09:00 UTC and nobody notices until the clocks change. Set the server's timezone deliberately, or put the offset where the scheduler can read it - fugit accepts a timezone after the five fields, which is why Solid Queue and GoodJob can honour one and crontab cannot.

    For a Rails app whose jobs are already Active Job, yes, and that is the direction Rails itself moved in. Solid Queue reads config/recurring.yml and GoodJob reads config.good_job.cron, both parse the same five fields through fugit, and both enqueue through the job backend you already run, so a missed schedule shows up in the same dashboard as a failed job. A crontab is still the right tool for work that is not a job at all - a database dump, a certificate renewal, log rotation - because those do not need Rails loaded and should not wait on a queue.

    Cron runs the job on either of them, which is the one rule that quietly doubles a schedule. As soon as both the day of the month field and the day of the week field hold something other than a star, Vixie cron stops treating them as a filter and treats them as two separate reasons to run, so `0 3 1 * 1` fires on the first of the month and again on every Monday. This page says so above the run list whenever you write a schedule that trips it.

    Cron hands a job almost none of the shell you get when you log in. There is no bundler context, often no rbenv or rvm shim on the path, and no RAILS_ENV, so `rails` either is not found or boots in development against the wrong database. Give the line an absolute path into the current release, load the environment you mean, and send the output somewhere you can read it. The whenever gem does this part for you, which is most of the argument for keeping schedule.rb under version control instead of editing crontab by hand.

    Start creating your next app now