
Sidekiq is a background job framework for Ruby that processes jobs concurrently on threads, backed by Redis. In Rails, add gem "sidekiq", set config.active_job.queue_adapter = :sidekiq, enqueue with perform_async, and run the sidekiq process alongside your app server. Sidekiq 8 requires Ruby 3.2+ and Redis 7+.
That answer covers the mechanics. This guide covers the rest: writing, scheduling, and prioritizing jobs, when Solid Queue is the better choice for a Rails 8 app, and the three metrics that flag a queue in trouble. Every job snippet was run against Sidekiq 8.1 on Ruby 3.4.
The Basics of Sidekiq
Web requests should finish in milliseconds. Sending an email, resizing an image, or importing a CSV file takes longer than that. Sidekiq moves that work out of the request cycle. Your app enqueues a job and responds to the user, while a separate Sidekiq process performs the work in the background.
Each Sidekiq process runs jobs on a pool of threads, so one process can work many jobs concurrently. That thread-based model separates Sidekiq from older single-threaded Ruby job runners: a handful of processes can deliver high throughput.
One naming note will save you confusion in older tutorials: job classes
include Sidekiq::Job. The Sidekiq::Worker name has been deprecated
since Sidekiq 6.3, because “worker” could mean a process, a thread, or a
job class.
Redis, an in-memory data store, acts as the queue. The Sidekiq client in your app pushes a small JSON payload to Redis, and Sidekiq server processes pull payloads off and execute them. Sidekiq 8 also accepts Valkey 7.2+ or Dragonfly 1.27+ as drop-in Redis alternatives, per the project README.
Sidekiq 8 itself brought three headline changes, per the Sidekiq changelog: a Web UI rewritten from scratch without the Bootstrap framework, job profiling with Vernier behind a new Profiles tab, and job metrics retained for up to 72 hours.
Note: Sidekiq depends on Redis for storage, but we won’t get into Redis server configuration and tuning in this article.
Installation and Setup of Sidekiq for Rails
Sidekiq 8 requires Ruby 3.2 or later, and a Redis 7.0+ server (or Valkey 7.2+, or Dragonfly 1.27+). With those in place, add Sidekiq to your application’s Gemfile:
gem "sidekiq"Then install it:
bundle installNext, tell Active Job to use Sidekiq as its backend. Open
config/application.rb and add this line inside the application class:
config.active_job.queue_adapter = :sidekiqClasses that include Sidekiq::Job don’t need this setting; they talk to
Sidekiq directly. The adapter line is what routes Active Job features, like
mailers with deliver_later, through Sidekiq.
By default, Sidekiq connects to Redis at localhost:6379, and it honors
the REDIS_URL environment variable. To configure the connection
explicitly, create an initializer at config/initializers/sidekiq.rb:
Sidekiq.configure_server do |config|
config.redis = { url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0") }
end
Sidekiq.configure_client do |config|
config.redis = { url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0") }
endThe server block configures the Sidekiq process that executes jobs; the client block configures your Rails processes that enqueue them.
Finally, Sidekiq runs as its own process, alongside your app server. Start it from your project directory:
bundle exec sidekiqIn production, run this under your process manager (a Procfile entry, a
systemd unit, or a container) so it restarts with your deploys.
Setting Up a Sidekiq Job
A Sidekiq job is a plain Ruby class with two ingredients: the
Sidekiq::Job module and a perform method.
class WelcomeEmailJob
include Sidekiq::Job
def perform(user_id)
# The work to run in the background goes here.
end
endTo queue a job, call perform_async on the class:
WelcomeEmailJob.perform_async(42)This pushes the job to the default queue and returns a job ID. The
arguments you pass are serialized to JSON, stored in Redis, and handed to
perform when a Sidekiq process picks the job up.
That JSON round-trip is why you should pass small, plain values: strings,
numbers, booleans. Pass a user_id instead of a User object, and look
the record up fresh inside perform.
Creating Your First Sidekiq Job
Let’s make a job that does something visible:
class HelloNameJob
include Sidekiq::Job
def perform(name, times)
times.times do
puts "Hello, #{name}!"
end
end
endHelloNameJob takes two arguments: name and times. When performed, it
prints a greeting to the console the specified number of times. In a real
application, perform would hold any code you want off the request path.
Enqueue it with the arguments perform expects:
HelloNameJob.perform_async("Jeff", 5)This enqueues a job that prints “Hello, Jeff!” five times, as soon as a Sidekiq thread is free.
To run a job later instead of now, use perform_in or perform_at:
HelloNameJob.perform_in(5.minutes, "Meredith", 3)
HelloNameJob.perform_at(2.days.from_now, "Jeff", 2)Here, Meredith gets her three greetings in five minutes, while Jeff waits two days for his. Scheduled jobs sit in a separate Redis sorted set until they’re due, then move to their queue.
Advanced Sidekiq Usage
Enqueueing work is half the story. Sidekiq also gives you per-job control over what happens on failure and which jobs go first.
Automatic Job Retries
If a job raises an unhandled exception, Sidekiq retries it automatically with exponential backoff: by default, 25 attempts spread over about 20 days, per the Sidekiq error-handling documentation. Transient failures, like a flaky external API, often heal on a later attempt.
You can tune the retry count per job with sidekiq_options:
class SyncInvoicesJob
include Sidekiq::Job
sidekiq_options retry: 10
def perform(account_id)
# ...
end
endHere, a failing job is retried ten times before Sidekiq gives up. A job that exhausts its retries moves to the Dead set, where it waits for you to inspect, fix, and retry it by hand from the Web UI.
Retries have one design consequence: a job can run more than once. Write
perform so a second execution doesn’t double-charge a card or send a
duplicate email. Idempotency keys and database constraints both work.
Job Prioritization
Not all jobs are equally urgent. A password reset email should not wait
behind ten thousand analytics rollups. Sidekiq handles this with named
queues; assign a job to one with sidekiq_options:
class ChargePaymentJob
include Sidekiq::Job
sidekiq_options queue: "critical"
def perform(payment_id)
# ...
end
endJobs from ChargePaymentJob now land in the critical queue. Then tell
the Sidekiq process which queues to work, and how to weigh them:
bundle exec sidekiq -q critical,3 -q default,1With weights, Sidekiq checks critical three times as often as default,
so urgent jobs move first while the rest still make progress. Listing
queues without weights processes them in strict order instead, which risks
starving the later queues. Queue weighting is covered in the
advanced options documentation.
Sidekiq vs Solid Queue in Rails 8
New Rails 8 applications come with Solid Queue configured as the Active Job backend, so the question is now “should I replace the default?” The short answer: stay on Solid Queue when job volume is modest and you’d rather not operate Redis; move to Sidekiq for high throughput, automatic retries with backoff, or its ecosystem of extensions.
Here’s how the two compare:
| Sidekiq 8 | Solid Queue | |
|---|---|---|
| Backing store | Redis 7+ (or Valkey, Dragonfly), in memory | Your database (MySQL, PostgreSQL, or SQLite), using FOR UPDATE SKIP LOCKED |
| Throughput | Tens of thousands of jobs per second in the project’s sidekiqload benchmark | Bound by database write capacity; ample for most apps, lower at high volume |
| Retries | Built in: exponential backoff, 25 attempts by default, Dead set for failures | Delegated to Active Job’s retry_on; unhandled failures wait in a table for manual retry |
| Ecosystem | Over a decade of middleware and extensions, plus commercial Pro and Enterprise tiers | Newer and smaller; pairs with Mission Control for a jobs dashboard |
| Rails-default status | Opt-in: set config.active_job.queue_adapter = :sidekiq | Configured by default in new Rails 8 apps |
The comparison is lower-stakes than it looks, because both run under
Active Job. If your jobs use the Active Job API, switching backends later
means changing the adapter and migrating in-flight jobs, not rewriting job
classes. Jobs that include Sidekiq::Job directly are faster, but tie
you to Sidekiq. Sidekiq’s README benchmark shows native jobs processing
roughly twice as fast as their Active Job equivalents.
Monitoring Sidekiq in Production
Background jobs fail quietly. A user notices a slow page immediately; nobody notices a stuck queue until the password reset emails stop arriving. That makes monitoring less an add-on and more part of the setup.
The Built-In Web UI
Sidekiq ships with a web dashboard showing queues, in-progress jobs,
retries, scheduled jobs, and the Dead set. In Sidekiq 8, it also carries a
Metrics tab with up to 72 hours of job metrics and a Profiles tab for
Vernier job profiles. Mount it in config/routes.rb:
require "sidekiq/web"
mount Sidekiq::Web => "/sidekiq"The dashboard then lives at /sidekiq. It exposes job arguments and lets
visitors delete or retry jobs, so never leave it public: wrap the mount in
an authentication constraint before deploying.
The Three Metrics That Matter
Dashboards offer plenty of numbers. Three of them predict trouble:
Queue latency, not queue size. Latency is how long the oldest job in a queue has been waiting. Ten thousand queued jobs can be fine if they drain in seconds, while fifty jobs with climbing latency mean work is arriving faster than your workers finish it. Sidekiq exposes latency directly:
require "sidekiq/api"
Sidekiq::Queue.new("critical").latency # seconds the oldest job has waitedWatch latency per queue, and alert on it; a latency alert on critical
tells you password resets are late while the number is still small.
Job failure and retry rate. A steady trickle of retries is normal; a rising rate is an early warning. Growth in retries usually points at a failing dependency, and it precedes a pile-up in the Dead set. Track the trend, not the totals.
Worker memory. Sidekiq processes are long-lived, so memory bloat accumulates: large payloads, unbounded caches, and leaky libraries show up as a worker’s resident set size (RSS) climbing until the process restarts or is killed. Memory tracked per process over time separates a leak from a hungry job.
Where the Web UI Stops
The Web UI is a snapshot: limited history, no alerts. For production, pair it with an APM tool that stores these metrics over time.
AppSignal for Ruby does this for Sidekiq out of the box. The gem hooks into Sidekiq’s server middleware without extra configuration, and the integration also covers jobs enqueued through Active Job. You get a prebuilt Sidekiq dashboard with job duration, failed and retried jobs, queue latency, and Redis memory, plus anomaly alerts on any of those metrics:

The same setup works for Sidekiq apps that don’t run on Rails; the integration documentation covers the plain-Ruby configuration.
The Sidekiq Web UI shows what’s queued right now, but not how queue latency trends or why a worker’s memory keeps climbing. AppSignal’s Sidekiq integration captures those automatically — latency, failures, and memory per worker — so the jobs you just built stay observable in production.
A Sidekiq Use Case
Email is the classic Sidekiq workload. Messages with attachments or many recipients take real time to send, and none of that time belongs in a web request. Here’s a dedicated Sidekiq job for it:
class ResetPasswordJob
include Sidekiq::Job
def perform(user_id)
UserMailer.reset_password_email(user_id).deliver_now
end
endResetPasswordJob calls deliver_now because the job itself is already in
the background; enqueue it with ResetPasswordJob.perform_async(user.id)
from a controller or model.
In a typical Rails application, though, you often don’t need a dedicated
job class for mail. Action Mailer integrates with Active Job, and with the
Sidekiq adapter configured, deliver_later enqueues through Sidekiq on its
own:
class UserMailer < ApplicationMailer
def reset_password_email(user_id)
@user = User.find(user_id)
mail(to: @user.email, subject: "Reset your password")
end
end
# elsewhere in your application
UserMailer.reset_password_email(user.id).deliver_laterReach for a hand-written Sidekiq job when you want control the Active Job
wrapper doesn’t give you: custom sidekiq_options for retries and queues,
or the raw throughput of native jobs. For everything else, deliver_later
keeps your code shorter.
Wrapping Up
In this post, we set up Sidekiq 8 in a Rails application, wrote and scheduled jobs, and tuned retries and queue priorities.
We also compared Sidekiq with Solid Queue, the Rails 8 default, and built a monitoring habit around the three metrics that matter in production: queue latency, failure rate, and worker memory.
Happy coding!
P.S. If you’d like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!
Frequently asked questions
- What tools can I use to monitor Sidekiq job performance?
- Sidekiq ships with a Web UI that shows queues, retries, and scheduled jobs in real time. For history and alerting, an APM tool like AppSignal tracks queue latency, job failure rates, and per-worker memory, and alerts you when a threshold is crossed.
- Should I use Sidekiq or Solid Queue in Rails 8?
- Use Solid Queue if you want the Rails default and no Redis dependency; it stores jobs in your database. Use Sidekiq for high job volume, automatic retries with exponential backoff, and a deep ecosystem. Both work through Active Job, so switching later is contained.
- How do I set up Sidekiq in a Rails app in 2026?
- Add the sidekiq gem to your Gemfile, point config.active_job.queue_adapter to :sidekiq, and run a Sidekiq process next to your app server with bundle exec sidekiq. Sidekiq 8 needs Ruby 3.2 or newer and Redis 7 or newer.
Published , Updated
Wondering what you can do next?
- Subscribe to our Ruby Magic newsletter and never miss an article again.
- Start monitoring your Ruby app with AppSignal.
- Share this article on social media

Jeff Morhous
Our guest author Jeff Morhous is a Software Engineer writing code and fixing bugs to help patients get the medications they need. These days he's focused on making web applications with Ruby on Rails, but in the past he's used Swift, Java, and Kotlin for iOS and Android development.
All articles by Jeff MorhousBecome our next author!
AppSignal monitors your apps
AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!


