Configuration¶
Broker¶
app.config_from_object({
# Valkey/Redis
"broker_url": "redis://localhost:6379/0", # or valkey://
# RabbitMQ (AMQP)
# "broker_url": "amqp://guest:guest@localhost:5672//",
})
Result backend¶
Without a result backend, task return values are discarded. Set result_extended = True to store extra metadata (task name, args, kwargs, worker hostname).
Transport options¶
result_backend_transport_options is passed through to the backend. The
Valkey/Redis backend reads one key of its own:
| Key | Default | Description |
|---|---|---|
additional_connection_errors |
() |
Extra exception classes, or dotted paths to them, to treat as connection errors and retry |
A proxy or a managed Valkey/Redis service in front of the server raises its own exception type when it drops a connection, and the retry machinery only knows the client library's types. List those here so they are retried instead of surfacing as a hard failure. An entry that does not resolve to an exception class is skipped with a warning rather than failing backend construction:
app.config_from_object({
"result_backend_transport_options": {
"additional_connection_errors": ["mypackage.errors.ProxyDisconnected"],
},
})
Pool sizing¶
The asyncio pool is sized by three settings, not by worker_concurrency:
| Setting | CLI flag | Default | Description |
|---|---|---|---|
worker_loop_workers |
--loop-workers |
1 | Threads running an event loop |
worker_loop_concurrency |
--loop-concurrency |
10 | Concurrent async tasks per loop worker |
worker_sync_workers |
--sync-workers |
1 | Threads for sync tasks |
Async task capacity is worker_loop_workers × worker_loop_concurrency. Sync tasks
run in a separate pool of worker_sync_workers threads. On Python 3.14t both pools
run in parallel.
worker_concurrency and -c are accepted for compatibility with upstream Celery
and size the prefetch count, but the asyncio pool does not read them for capacity.
Left unset, the concurrency is the pool's slots,
worker_loop_workers × worker_loop_concurrency + worker_sync_workers.
Worker settings¶
| Setting | Default | Description |
|---|---|---|
worker_max_tasks_per_child |
None | Restart worker after N tasks |
worker_max_memory_per_child |
None | Restart worker if RSS exceeds N KiB |
task_soft_time_limit |
None | Soft time limit in seconds |
task_time_limit |
None | Hard time limit in seconds |
task_acks_late |
False | Acknowledge tasks after execution |
worker_soft_shutdown_timeout |
0.0 | Seconds to let active tasks finish before cancelling them on shutdown; 0 cancels straight away |
worker_enable_soft_shutdown_on_idle |
False | Wait out worker_soft_shutdown_timeout even with no active tasks |
worker_deduplicate_successful_tasks |
False | On a redelivered message, look the task id up in the result backend first and skip it if it already succeeded. Needs task_acks_late and a persistent backend |
Acknowledgement¶
task_acks_late moves the acknowledgement to after the task has run, so a task
whose worker dies mid-execution is redelivered. These settings decide what
happens to a message when the task that ran late-acknowledged did not succeed:
| Setting | Default | Description |
|---|---|---|
task_acks_on_failure |
None | Acknowledge the message when the task raises |
task_acks_on_timeout |
None | Acknowledge the message when the task hits its time limit |
task_acks_on_failure_or_timeout |
True | Covers both of the above; deprecated in 6.0, to be removed in 7.0 |
None means "fall back to task_acks_on_failure_or_timeout", so the default
behaviour is unchanged from upstream: a failed or timed-out task is
acknowledged and not redelivered. Set one of the two to False to have that
kind of outcome redelivered instead. All three only apply to tasks that are
acknowledged late; without task_acks_late the message is already gone by the
time the task runs. The per-task attributes acks_on_failure and
acks_on_timeout override the app setting.
Prefetch¶
| Setting | CLI flag | Default | Description |
|---|---|---|---|
worker_prefetch_multiplier |
--prefetch-multiplier |
4 | Multiplied by the concurrency to get the prefetch count the worker asks the transport for |
worker_enable_prefetch_count_reduction |
True | After a connection loss, reconnect with a prefetch count reduced by the number of tasks still running |
The prefetch count caps how many messages the worker holds unacknowledged. On AMQP, RabbitMQ enforces it for each queue the worker consumes. Valkey and Redis cannot push, so there the transport enforces it, for all of the worker's queues together: one consume round-trip claims as many messages as the cap has room for, up to 100, and a worker at the cap claims nothing more until it acks or rejects one.
A task acknowledged late holds its slot while it runs. With task_acks_late,
a prefetch count below what the pool can run at once leaves the rest of the
pool idle. The count follows the pool's slots unless -c is set, so only a
-c below worker_loop_workers × worker_loop_concurrency + worker_sync_workers
can cause it.
With worker_enable_prefetch_count_reduction on, a worker that reconnects
while N tasks are still running comes back with the count lowered by one
multiplier for each of them. Every task acked or rejected after that gives one
multiplier back until the count is whole again, so the worker does not claim a
second full window on top of the work already in hand.
Connection loss¶
| Setting | Default | Description |
|---|---|---|
worker_cancel_long_running_tasks_on_connection_loss |
False | On losing the broker connection, cancel the running tasks that have task_acks_late set |
A late-acknowledged task cannot be acknowledged once the connection is gone, so the broker redelivers it and the work is done twice. Turning this on cancels those tasks instead and lets the redelivery be the only run. It is off by default, and leaving it off logs a pending-deprecation warning on every reconnect.
Serialization¶
app.config_from_object({
"task_serializer": "json", # json, pickle, msgpack, yaml
"result_serializer": "json",
"accept_content": ["json"],
})
Compression¶
app.config_from_object({
"task_compression": "gzip", # gzip, bzip2, lzma, zstd
"result_compression": "gzip",
})
brotli joins that list when the brotli extra is installed.
task_compression compresses the task message body, and the broker carries the
method in a message header. result_compression compresses the stored result,
which has no header, so the method is written in front of the payload instead.
A compressed result is binary, so result_compression is only honoured by a
backend that hands arbitrary bytes back unchanged. The Valkey/Redis, filesystem
and cache backends do, except Valkey/Redis with decode_responses in its URL,
set to any value, false included. A backend that cannot ignores the setting
and warns once, when it is built. A client with decode_responses cannot read
a result that another worker stored compressed either, so drop the parameter
from every result backend URL before turning compression on.
Reading is driven by the stored payload rather than by the setting, so results written before compression was turned on stay readable, and a reader with no compression configured can still read a compressed result. Roll the setting out to readers first if writers and readers run different versions.
An unrecognised method raises ImproperlyConfigured when the backend is built.
Task autodiscovery¶
Or with Django:
CLI options¶
celery -A app worker [OPTIONS]
Options:
--loglevel=INFO Log level (DEBUG, INFO, WARNING, ERROR)
-E / --task-events Enable task events for Flower
--loop-workers=N Number of async loop workers (default: 1)
--loop-concurrency=N Max concurrent async tasks per loop worker (default: 10)
--sync-workers=N Number of sync worker threads (default: 1)
-c / --concurrency=N Sizes the prefetch count, not the pool (default: the pool's slots)
--max-tasks-per-child=N Restart after N tasks
--max-memory-per-child=N Restart if RSS exceeds N KiB
--prefetch-multiplier=N Prefetch count per concurrency slot (default: 4)