rufus-scheduler: an in-process job scheduler for Ruby, and what it is not
scheduler for Ruby (at, in, cron and every jobs)
At a glance
- What is it?
- Five kinds of jobs driven by threads inside your own process, built on fugit and et-orbi. It does not persist anything, and the README is unusually direct about that.
- Who is it for?
- rufus-scheduler is easy to like precisely because it does not pretend to be more than it is. An in-process, in-memory scheduler on threads, no persistence, and a README with a dedicated non-features section saying so before you discover it in production.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 106 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The shortest useful program is four statements
The quickstart is the whole introduction, and it is worth reading for what it leaves out:
require 'rufus-scheduler'
scheduler = Rufus::Scheduler.new
scheduler.in '3s' do
puts 'Hello... Rufus'
end
scheduler.joinThere is no configuration, no daemon, and no external dependency to install. The scheduler is an object in your process and the jobs are threads. `scheduler.join` blocks the calling thread against the scheduler thread, which is what keeps a plain Ruby script alive long enough for the job to fire.
The comment attached to `join` is the part worth noticing. It tells you to remove the call when scheduling from a web application initializer, which is the single most common way to get this gem's behaviour wrong. In a Rails process something else is already keeping the process alive, and a blocking join in an initializer turns a background scheduler into a boot-time hang.
Time parsing is delegated rather than reimplemented. The README credits fugit for parsing time strings, et-orbi for pairing time, and tzinfo for timezones. Those are the same three libraries Active Record uses, which is why scheduling works with familiar strings instead of a bespoke syntax you have to look up.
Five job types, and the difference between every and interval
Five kinds of job are supported, and the first two trigger exactly once:
scheduler.in '10d' do
# do something in 10 days
end
scheduler.at '2030/12/12 23:30:00' do
# do something at a given point in time
end
scheduler.every '3h' do
# do something every 3 hours
end
scheduler.every '3h10m' do
# do something every 3 hours and 10 minutes
end
scheduler.cron '5 0 * * *' do
# do something every day, five minutes after midnight
endThe `every` and `cron` variants both repeat, and the cron form is ordinary crontab syntax with a pointer to `man 5 crontab` rather than a restatement of the format.
The subtle one is `interval`, which the 3.0 notes describe as the difference between "every 10 minutes, do this" and "do that, then wait for 10 minutes, then do that again, and so on". That is the whole difference, and it matters more than it sounds. With `every`, a job that takes seven minutes on a ten-minute schedule overlaps itself and you have already lost. With `interval`, the next run is scheduled after the previous one finishes, so a slow job degrades into running less often instead of running concurrently.
The README also notes that scheduling a handler instance or a handler class is fine, not just a block. Most examples use blocks, so it is easy to assume that is the only interface.
What the non-features section rules out
The README has a section titled non-features and it is the most useful part of the document. rufus-scheduler out of the box is an in-process, in-memory scheduler using threads. It does not persist your schedules. When the process is gone, the scheduler instance with it is gone, and the schedules are gone.
Then the sentence that settles a lot of arguments: please note, rufus-scheduler is not a cron replacement.
The follow-on detail is precise about lifecycle. A scheduler instance keeps scheduling for as long as it stays reachable among the objects in a Ruby process, and the only way to stop it is to call `#shutdown`. There is no other documented lever, which means a scheduler you failed to store in a variable can keep a process alive, and a scheduler you did store keeps running until shutdown whether or not you want it to.
That in-memory property is not a flaw so much as a boundary. Anything that must survive a deploy, a crash or a reboot is the wrong shape of problem for this gem, and the README points you at alternatives for exactly that case rather than leaving you to discover the difference in production.
Lockfiles, discard_past, and the post-3.0 behaviour changes
The notable changes list for 3.0 is where the operational story is. Two mechanisms stand out because they solve the failure modes that in-process schedulers actually hit.
The first is the lockfile mechanism, taking `true` or a filename, introduced to prevent multiple schedulers from executing. This is the answer to a recurring problem: with several application instances or several processes, every one of them starts a scheduler and the same job runs several times over. The README links it from its FAQ under the heading "the job triggers twice".
The second is `discard_past`, which is on by default. The explanation is concrete: if the scheduler host sleeps for an hour and an `every '10m'` job is set, it triggers once at wakeup, not six times. In 2.x this was off by default, so an upgraded application can behave differently after a restart following a suspension, which is the kind of change worth reading about before deploying.
The rest of the list is mostly renames and sharpenings. `every('100')` now means 100 seconds rather than 0.1 seconds, aligning the gem with Ruby's own `sleep(100)`. The scheduler no longer catches all of Exception, only StandardError. `on_exception` became `on_error`, which by default prints to `$stderr` where it used to print to `$stdout`. `TimeOutError` was renamed to `TimeoutError`. And `#on_pre_trigger` and `#on_post_trigger` callbacks were added.
Where cron, Clockwork and Whenever fit against it
The README keeps two lists, related gems and similar gems, and the distinction between them is worth preserving. Related gems extend rufus-scheduler: ruby-clock as a separate clock process, Puma-Rufus-Scheduler as a Puma plugin to run it, and Schked as a framework-agnostic wrapper for recurring jobs. Schked in particular is the honest answer to the complaint that rufus-scheduler is too low-level for Rails.
Similar gems are alternatives rather than extensions. Whenever is the important one, and the README's summary of it is a one-line argument: let cron call back your Ruby code, trusted and reliable cron drives your schedule. Clockwork is described as rufus-scheduler inspired, and Crono as an in-Rails cron scheduler. PerfectSched goes further still, a highly available distributed cron built on Sequel and more.
That list is the real decision procedure. If a schedule must not be missed, the honest answer is a durable external scheduler. If a job can be lost when the process restarts and starting without ceremony matters more, rufus-scheduler is the better fit. The related-gems column exists for the common middle case where you want rufus's ergonomics but not to wire a scheduler into an initializer by hand.
One more piece of context is version-specific. The README opens by pointing readers who need the older documentation to the 2.x README, specifically noting that Dashing is stuck on rufus-scheduler 2.0.24. That is the practical reason to check which line you are on before reading further.
A repository that reads like the author's notebook
The prose style here is unusual enough to be worth remarking on, because it tells you what kind of maintainer you are dealing with. There is a section on getting help that opens by saying people can help you, but first help them help you, and do not waste their time. It asks for a complete description of an issue, points at how to report bugs effectively, and asks for it twice. The FAQ includes an entry titled "I want a refund".
None of that is padding. It is the visible result of a popular gem accumulating low-quality issues, and the author has decided to manage that at the source rather than in the tracker. There is also a note that bugs belong in the issue tracker and that a Stack Overflow question is better only if nothing is wrong with rufus-scheduler itself.
The repository layout backs this up. Beyond the usual gemspec, `Gemfile` and `LICENSE.txt`, there are `CHANGELOG.md`, `CREDITS.md`, a `TODO.txt`, `misc/`, and a `Makefile` whose targets are refreshing: `count_lines` counts non-comment lines across `lib` and `spec` separately, `gemspec_validate` evaluates the gemspec and validates it before a build, and the build target shells out to `gem build` and `gem push --otp`. Tracking lines of code as a make target is a habit from a different era, and it is still there.
The project is MIT licensed, not archived, with its last push on 2026-06-22.
Editorial conclusion
rufus-scheduler is easy to like precisely because it does not pretend to be more than it is. An in-process, in-memory scheduler on threads, no persistence, and a README with a dedicated non-features section saying so before you discover it in production. That honesty extends to the parts that usually get glossed over: the 3.0 line was a complete rewrite, `every` changed units, exceptions are no longer swallowed wholesale, and the author links his own bug reporting guide rather than absorbing vague issues. The features that decide whether you want it are the ones absent from that list, namely the lockfile mechanism that stops two schedulers running the same job, `discard_past` on by default so a sleeping host does not stampede, and the interval job type that measures from completion rather than from the previous start. If your schedule must survive a restart, read it against Whenever, which the README lists as the cron-driven alternative.
Frequently asked questions
Does rufus-scheduler replace cron?
No, and the README says so directly: please note, rufus-scheduler is not a cron replacement. It is an in-process, in-memory scheduler using threads, and it does not persist your schedules. When the process is gone the schedules are gone, which is why the README points at Whenever as the cron-driven alternative for schedules that must not be missed.
Which kinds of jobs does rufus-scheduler support?
Five: in, at, every, interval and cron. In and at jobs trigger once, either after a relative delay or at a point in time such as `2030/12/12 23:30:00`. Every and cron repeat on a fixed schedule. Interval repeats only after the previous run finishes, so a slow job runs less often instead of overlapping itself.
What changed in rufus-scheduler 3.0?
It was a complete rewrite and the EventMachine-based scheduler is gone. `every('100')` now means 100 seconds rather than 0.1 seconds, matching Ruby's own `sleep(100)`. Only StandardError is caught rather than all of Exception, `on_exception` became `on_error` and now prints to $stderr, `TimeOutError` was renamed to `TimeoutError`, and `#on_pre_trigger` and `#on_post_trigger` were added.
Why does my scheduled job trigger twice?
The usual cause is more than one scheduler running, which the README links to its lockfile answer. The 3.0 line introduced a lockfile mechanism taking `true` or a filename to prevent multiple schedulers from executing. The other thing to check is `discard_past`, which is on by default, so a host that slept through several intervals fires once at wakeup rather than catching up.
How do I use it in a Rails application?
Do not leave `scheduler.join` in an initializer, which the quickstart comment calls out explicitly. In a web application something else already keeps the process alive, and a blocking join there turns a background scheduler into a boot-time hang. The README also lists Schked as a framework-agnostic wrapper for recurring jobs, and Puma-Rufus-Scheduler as a Puma plugin.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jmettraux-rufus-scheduler)