# RQ: a Redis-backed job queue for Python that stays out of your way

> RQ queues ordinary Python functions in Redis and runs them in worker processes. It is aimed at teams that want background jobs without a broker to operate, and its main trade-off is that Redis is both the transport and the only place job state lives.

**rq/rq** — Simple job queues for Python

- Repository: https://github.com/rq/rq
- Website: https://python-rq.org
- Stars: 10,696 · Forks: 1,501
- Language: Python
- License: NOASSERTION
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/rq-rq

## The problem RQ solves, and the developers it fits

A web request should not wait on a function that fetches a URL, resizes an image or sends mail. RQ takes that function call, serialises it into Redis, and lets a separate worker process run it later. The README describes the library as "simple" and "designed to have a low barrier to entry", and the getting started example is exactly that: define a plain function, build a Queue, call enqueue.

That simplicity is the product. There is no broker daemon beyond Redis or Valkey, no schema to migrate, no separate scheduler process to keep alive unless you choose to run one. The intended audience is Python developers who already have Redis in their stack and want to move blocking work off the request path without learning a new programming model. The project classifiers list Python 3.10 through 3.14, so the supported range is current rather than legacy.

The fit is narrower than "any background job". RQ runs functions that exist in importable Python modules, which means the worker must be able to import your code. If your jobs are defined inside a web application, the worker deployment has to ship the same code, and the README does not address that deployment question.

## How a job travels from enqueue to worker

The flow has three parts: a Redis connection, a Queue, and a worker process. The connection is a standard redis-py client. The queue is a named list in Redis. Enqueuing pushes a job onto that list and returns a Job object. A worker started with the rq command pops jobs from the queues it was told to watch and executes the referenced function.

The README's own example makes the indirection explicit. You import the function and pass it to enqueue with its arguments, and RQ records the function's import path plus the arguments rather than the callable itself. That is why the worker needs the same code available at run time.

Prioritisation is handled two ways. A single queue can be pushed from the front with at_front=True, or you can split work across named queues and start a worker with a priority list. The README's example starts a worker that listens to both high and low queues and processes high first. Because queues are just names, you can also run separate workers per queue and scale them independently, which the README notes as a way to match worker count to job volume.

Scheduling and repetition sit on top of the same mechanism. enqueue_at and enqueue_in place a job to run later, and a Repeat object re-enqueues a job after a successful run, either at a fixed interval or at a list of intervals such as [5, 10, 15].

## Installing RQ and running a first worker

The package is named rq on PyPI and its dependencies are click, croniter and redis. Installing it also puts an rq command on your path, since pyproject.toml declares scripts.rq = "rq.cli:main".

You need a Redis or Valkey server first. The README starts one with redis-server:

```bash
$ redis-server
```

Then install the library:

```bash
$ pip install rq
```

Define a function in a module the worker can import, and enqueue it. The README's example fetches a URL and counts words:

```python
import requests

def count_words_at_url(url):
    """Just an example function that's called async."""
    resp = requests.get(url)
    return len(resp.text.split())
```

Create a queue and enqueue the call. The job object is what you use later to inspect status or retrieve the return value:

```python
from redis import Redis
from rq import Queue
from my_module import count_words_at_url

queue = Queue(connection=Redis())
job = queue.enqueue(count_words_at_url, 'https://stamps.id')
```

Start a worker against the default queue. With no queue names given, the worker listens to the default queue; naming queues is how the priority example works:

```bash
$ rq worker high low
```

The worker process should pick up the job and print its result. If nothing runs, the usual causes are that the worker cannot import my_module, or that it is connected to a different Redis database than the one you enqueued into.

## Retries, uniqueness and rate limits are opt-in per job

RQ does not apply a global retry policy. You pass a Retry object at enqueue time, and the README shows two forms: Retry(max=3) requeues immediately, while Retry(max=3, interval=[10, 30, 60]) waits between attempts. The interval list has to line up with the number of retries, which is a detail the README leaves to the docs.

Uniqueness works through a job_id plus unique=True. The README's example uses job_id='welcome-42' with unique=True to stop a duplicate welcome email being enqueued. This is a deduplication guard at enqueue time, not a lock inside the job, so it does not stop two workers from running the same job if the same id is forced through by other means.

Rate limiting arrived in RQ 2.11.0 and is expressed as a concurrency limit on a shared key. The README shows RateLimit(key='reports', concurrency=2), which caps how many jobs sharing that key run at once. It is concurrency-based, not a token bucket: it limits simultaneous execution, not requests per second.

Webhooks remove the need for a callback function. You attach a Webhook with a URL and a job_status of finished or failed, and RQ sends the HTTP request when that state is reached. The README points to the docs for the full behaviour, including anything about payload shape or retry of the webhook call itself.

## Cron and interval scheduling without an external scheduler

From RQ 2.5 onward the package ships its own scheduler. You write a configuration file that calls cron.register for each periodic job, giving it a queue name and either an interval in seconds or a standard cron expression. The README registers a cleanup every 1800 seconds and a report every 21600 seconds, and separately shows cron='0 3 * * *' for a nightly backup and cron='0 8 1 * *' for a monthly report.

You then run the scheduler as its own process:

```bash
$ rq cron cron_config.py
```

Cron jobs accept webhooks too, so a scheduled run can ping a monitoring endpoint on finish or failure. The practical consequence is that you now have two long-running processes to supervise: the worker and rq cron. The README does not describe what happens to a missed schedule if the cron process is down at the moment a job was due, so treat catch-up behaviour as something to verify in the docs before you rely on it for anything with a deadline.

The scheduler also means croniter is a hard dependency, which is visible in pyproject.toml alongside click and redis.

## Where RQ is the wrong tool

The queue is Redis. If Redis is flushed, restarted without persistence, or lost, the jobs in it are gone. RQ does not offer a durable log of job state independent of Redis, so any workflow that needs exactly-once execution or an audit trail that survives the datastore is a poor match. A payments pipeline that must not double-charge, or a compliance job that must be provably executed once, belongs on a system with a durable log such as Kafka or a database-backed queue.

The second boundary is code deployment. Because jobs are referenced by import path, a worker running an older checkout can fail on a job enqueued by a newer one, or pick up a function whose signature changed. The README says nothing about versioning or draining workers during a deploy, so rolling out a breaking change to a job function is your problem to solve.

Third, RQ runs Python callables. If your work is in another language, or you need a workflow engine with branching and fan-in across heterogeneous steps, RQ gives you a queue and not a DAG. You would be building the orchestration layer yourself.

## RQ against Celery, and what the licence allows

Celery is the obvious alternative and the difference is architectural. Celery supports multiple brokers and result backends, including RabbitMQ, and its task model carries routing, chains, chords and a large configuration surface. RQ commits to Redis or Valkey as both transport and state store, and its API is a Queue plus an enqueue call. If you need a broker you can swap out, or workflows composed of chained and grouped tasks, Celery covers ground RQ deliberately does not. If you already run Redis and your jobs are independent function calls, RQ asks you to learn far less.

On licensing, pyproject.toml declares license = "BSD-2-Clause" and the classifier "License :: OSI Approved :: BSD License", while the repository metadata reports NOASSERTION. The package metadata is the more specific statement, and the two-line BSD licence is permissive, but this is a factual note rather than legal advice; confirm the terms in the LICENSE file for your own distribution.

Upgrade cost is low on the surface. The project keeps a CHANGES.md and publishes releases frequently, with v2.12, v2.11 and v2.10 all appearing in 2026. The dependency floor is redis>=5.0.1, so a Redis client older than 5 will block the install. The last push to the repository was on 2026-09-20.

## Conclusion

Adopt RQ when your background work is ordinary Python functions and you already run Redis or Valkey, and you want retries, scheduling and cron without a separate broker. Skip it when you need exactly-once execution or durable job state that survives a Redis flush, because the queue lives in Redis and a lost instance takes the jobs with it. Before committing, check your Redis version against the Redis >= 5 or Valkey >= 7.2 requirement, confirm your Python is 3.10 or newer, and read the docs page for the feature you depend on, since the README only sketches webhooks, unique jobs and rate limits.

## FAQ

### What does RQ stand for in the Python job queue?

The README expands it as Redis Queue: a Python library for queueing jobs and processing them in the background with workers, backed by Redis or Valkey.

### How do I install RQ and start an rq worker?

Install the package from PyPI with pip install rq, then run rq worker to process the default queue, or pass queue names such as rq worker high low to set priority order.

### Which Redis version does RQ require?

The README states RQ requires Redis >= 5 or Valkey >= 7.2, and pyproject.toml pins the client dependency at redis>=5.0.1.

### Does RQ support retries and scheduling?

Yes. Retry(max=3) or Retry(max=3, interval=[10, 30, 60]) handles failed jobs, enqueue_at and enqueue_in schedule a single run, and rq cron runs jobs registered with cron.register on an interval or cron expression.

## Sources

- [Issues](https://github.com/rq/rq/issues)
- [Project website](https://python-rq.org)
- [README](https://github.com/rq/rq/blob/master/README.md)
- [Releases](https://github.com/rq/rq/releases)
- [rq/rq on GitHub](https://github.com/rq/rq)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rq-rq
