Celery es una cola de tareas distribuida para Python: tu aplicación encola trabajos (enviar un email, generar un PDF, procesar una imagen) y uno o varios procesos worker los ejecutan en segundo plano. Celery no guarda los mensajes por sí mismo, sino que usa un broker, normalmente Redis o RabbitMQ. En este tutorial crearás un proyecto de Celery en Ubuntu 24.04 con Redis como broker, programarás tareas periódicas con Celery Beat, enviarás tareas a colas distintas, ejecutarás todo con systemd y lo monitorizarás con Flower. Al final verás cómo cambiar el broker a RabbitMQ.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 1 GB de RAM.
  • Un usuario no root con privilegios sudo. En los ejemplos se llama your_user; sustitúyelo por el tuyo.
  • Conocimientos básicos de Python 3.

Cómo encajan las piezas

ComponenteFunción
Aplicación (productor)Llama a tarea.delay() para encolar un trabajo
Broker (Redis o RabbitMQ)Almacena los mensajes de tareas hasta que un worker los recoge
WorkerProceso de Celery que ejecuta las tareas
Backend de resultadosGuarda el valor devuelto por cada tarea para consultarlo después (opcional)
Celery BeatPlanificador que encola tareas periódicas

Paso 1: Instalar Redis

Redis actuará como broker y como backend de resultados. Instálalo desde los repositorios de Ubuntu:

sudo apt update
sudo apt install redis-server

El paquete deja Redis escuchando solo en localhost, que es lo que queremos. Comprueba que responde:

redis-cli ping
PONG

Paso 2: Crear el proyecto e instalar Celery

Crea un directorio para el proyecto con un entorno virtual de Python:

sudo apt install python3-venv
mkdir -p ~/celery-demo/proyecto
cd ~/celery-demo
python3 -m venv venv

Instala Celery con las dependencias de Redis:

~/celery-demo/venv/bin/pip install "celery[redis]"

Comprueba la versión:

~/celery-demo/venv/bin/celery --version

La salida muestra la versión instalada, por ejemplo 5.6.3, seguida de su nombre en clave.

Paso 3: Definir la aplicación de Celery

La aplicación de Celery centraliza la configuración. Lee la URL del broker de una variable de entorno para poder cambiar a RabbitMQ más adelante sin tocar el código. Crea el archivo que convierte proyecto en un paquete de Python y después la aplicación:

touch ~/celery-demo/proyecto/__init__.py
nano ~/celery-demo/proyecto/celery_app.py
import os

from celery import Celery
from celery.schedules import crontab

app = Celery(
    "proyecto",
    broker=os.environ.get("CELERY_BROKER_URL", "redis://localhost:6379/0"),
    backend=os.environ.get("CELERY_RESULT_BACKEND", "redis://localhost:6379/1"),
    include=["proyecto.tasks"],
)

app.conf.update(
    timezone="Europe/Madrid",
    # Confirma la tarea al terminarla, no al recibirla: si el worker muere, se reintenta
    task_acks_late=True,
    # Cada proceso reserva solo una tarea a la vez; mejor reparto con tareas largas
    worker_prefetch_multiplier=1,
    # Los resultados se borran del backend tras 1 hora
    result_expires=3600,
    # Las tareas de email van a su propia cola
    task_routes={
        "proyecto.tasks.enviar_email": {"queue": "emails"},
    },
    # Tareas periódicas para Celery Beat
    beat_schedule={
        "limpiar-cada-5-minutos": {
            "task": "proyecto.tasks.limpiar_temporales",
            "schedule": crontab(minute="*/5"),
        },
    },
)

Ahora define las tareas:

nano ~/celery-demo/proyecto/tasks.py
import time

from proyecto.celery_app import app


@app.task
def sumar(a, b):
    return a + b


@app.task(autoretry_for=(ConnectionError,), retry_backoff=True, max_retries=5)
def enviar_email(destinatario):
    # Aquí iría el envío real; si lanza ConnectionError, Celery reintenta
    # con espera exponencial hasta 5 veces
    time.sleep(2)
    return f"email enviado a {destinatario}"


@app.task
def limpiar_temporales():
    print("Limpiando archivos temporales")
    return "ok"

autoretry_for y retry_backoff hacen que una tarea que falla por un error transitorio se vuelva a encolar con esperas crecientes, sin escribir la lógica de reintento a mano.

Paso 4: Ejecutar un worker y lanzar tareas

Arranca un worker en primer plano para probar. -Q celery,emails le indica que consuma la cola por defecto (celery) y la de emails:

cd ~/celery-demo
./venv/bin/celery -A proyecto.celery_app worker -Q celery,emails --loglevel=INFO

El arranque muestra la configuración, las colas y las tareas registradas:

[queues]
  .> celery           exchange=celery(direct) key=celery
  .> emails           exchange=emails(direct) key=emails

[tasks]
  . proyecto.tasks.enviar_email
  . proyecto.tasks.limpiar_temporales
  . proyecto.tasks.sumar

[... INFO/MainProcess] Connected to redis://localhost:6379/0
[... INFO/MainProcess] celery@servidor ready.

En una segunda terminal, encola tareas desde Python y espera sus resultados:

cd ~/celery-demo
./venv/bin/python -c "
from proyecto.tasks import sumar, enviar_email
r1 = sumar.delay(2, 3)
r2 = enviar_email.delay('[email protected]')
print(r1.get(timeout=10))
print(r2.get(timeout=10))
"
5
email enviado a [email protected]

En la terminal del worker verás cada tarea recibida y completada, con su duración:

[... INFO/MainProcess] Task proyecto.tasks.enviar_email[5b0c...] received
[... INFO/ForkPoolWorker-2] Task proyecto.tasks.enviar_email[5b0c...] succeeded in 2.003s: 'email enviado a [email protected]'

Detén el worker con Ctrl+C. Celery termina las tareas en curso antes de salir.

Paso 5: Ejecutar el worker con systemd

En producción, el worker debe arrancar con el sistema y reiniciarse si falla. Crea la unidad de systemd, sustituyendo your_user por tu usuario:

sudo nano /etc/systemd/system/celery-worker.service
[Unit]
Description=Celery worker
After=network.target redis-server.service

[Service]
Type=simple
User=your_user
Group=your_user
WorkingDirectory=/home/your_user/celery-demo
ExecStart=/home/your_user/celery-demo/venv/bin/celery -A proyecto.celery_app worker -Q celery,emails --concurrency=4 --loglevel=INFO
Restart=on-failure
RestartSec=5
# Da tiempo a las tareas en curso para terminar al parar el servicio
TimeoutStopSec=300

[Install]
WantedBy=multi-user.target

--concurrency=4 fija el número de procesos que ejecutan tareas en paralelo. Si lo omites, Celery usa uno por CPU.

Activa y arranca el servicio:

sudo systemctl daemon-reload
sudo systemctl enable --now celery-worker

Comprueba que el worker responde:

cd ~/celery-demo
./venv/bin/celery -A proyecto.celery_app status
->  celery@servidor: OK

1 node online.

Los registros del worker están en el journal:

sudo journalctl -u celery-worker -f

Paso 6: Programar tareas periódicas con Celery Beat

Celery Beat lee beat_schedule y encola cada tarea cuando le toca; los workers se encargan de ejecutarla. Guarda el estado de la planificación en un archivo, que ubicarás en /var/lib/celery-beat gracias a la opción StateDirectory de systemd. Crea la unidad:

sudo nano /etc/systemd/system/celery-beat.service
[Unit]
Description=Celery Beat
After=network.target redis-server.service

[Service]
Type=simple
User=your_user
Group=your_user
WorkingDirectory=/home/your_user/celery-demo
StateDirectory=celery-beat
ExecStart=/home/your_user/celery-demo/venv/bin/celery -A proyecto.celery_app beat --schedule=/var/lib/celery-beat/celerybeat-schedule --loglevel=INFO
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Arranca el servicio:

sudo systemctl daemon-reload
sudo systemctl enable --now celery-beat

Espera al siguiente múltiplo de 5 minutos y revisa el journal del worker:

sudo journalctl -u celery-worker --since "10 minutes ago" | grep limpiar_temporales
... Task proyecto.tasks.limpiar_temporales[9e1f...] received
... Task proyecto.tasks.limpiar_temporales[9e1f...] succeeded in 0.001s: 'ok'

Paso 7: Separar colas entre workers

Enrutar tareas a colas distintas permite que un tipo de trabajo no bloquee a otro: si llegan miles de emails, las tareas de la cola celery siguen procesándose. Hasta ahora un solo worker consume las dos colas. Para dedicar procesos a cada una, cambia el ExecStart de celery-worker.service para que consuma solo celery:

ExecStart=/home/your_user/celery-demo/venv/bin/celery -A proyecto.celery_app worker -Q celery -n general@%%h --concurrency=4 --loglevel=INFO

Copia la unidad como celery-emails.service y ajusta su ExecStart para la cola emails:

sudo cp /etc/systemd/system/celery-worker.service /etc/systemd/system/celery-emails.service
sudo nano /etc/systemd/system/celery-emails.service
ExecStart=/home/your_user/celery-demo/venv/bin/celery -A proyecto.celery_app worker -Q emails -n emails@%%h --concurrency=2 --loglevel=INFO

La opción -n da a cada worker un nombre único en el servidor. En los archivos de systemd, % se escribe %% porque systemd lo usa para sus propios especificadores; Celery recibe %h y lo sustituye por el nombre del host.

Aplica los cambios:

sudo systemctl daemon-reload
sudo systemctl restart celery-worker
sudo systemctl enable --now celery-emails
./venv/bin/celery -A proyecto.celery_app status
->  general@servidor: OK
->  emails@servidor: OK

2 nodes online.

Para ver cuántas tareas esperan en cada cola con Redis como broker, consulta la longitud de la lista correspondiente:

redis-cli -n 0 llen emails

Paso 8: Monitorizar con Flower

Flower es la interfaz web de monitorización de Celery: muestra workers, tareas en curso, tiempos de ejecución y errores. Instálalo en el entorno virtual:

~/celery-demo/venv/bin/pip install flower

Arráncalo escuchando solo en localhost y con autenticación básica. Sustituye your_strong_password por una contraseña robusta:

cd ~/celery-demo
./venv/bin/celery -A proyecto.celery_app flower --address=127.0.0.1 --port=5555 --basic-auth=admin:your_strong_password

Como no está expuesto a Internet, accede desde tu equipo con un túnel SSH. En tu máquina local:

ssh -L 5555:localhost:5555 your_user@your_server_ip

Abre http://localhost:5555 en el navegador e inicia sesión. En Workers verás general@servidor y emails@servidor, y en Tasks el historial de ejecuciones. Si quieres dejar Flower permanente, crea una unidad de systemd como las anteriores con este mismo comando.

Usar RabbitMQ como broker

RabbitMQ es preferible a Redis cuando necesitas garantías de entrega más estrictas o ya lo usas en tu infraestructura. Instálalo desde los repositorios de Ubuntu:

sudo apt install rabbitmq-server

Crea un usuario y un virtual host dedicados a Celery. Usa una contraseña solo alfanumérica, o codifícala para URL, porque irá dentro de la URL del broker:

sudo rabbitmqctl add_user celery your_strong_password
sudo rabbitmqctl add_vhost celery
sudo rabbitmqctl set_permissions -p celery celery ".*" ".*" ".*"

Celery incluye el cliente AMQP, así que no hace falta instalar nada más en Python. Indica el nuevo broker en las unidades de systemd de los workers, Beat y Flower, añadiendo esta línea en la sección [Service]:

Environment="CELERY_BROKER_URL=amqp://celery:your_strong_password@localhost:5672/celery"

Los resultados pueden seguir guardándose en Redis, que es más eficiente que RabbitMQ para esa función. Recarga y reinicia los servicios:

sudo systemctl daemon-reload
sudo systemctl restart celery-worker celery-emails celery-beat
sudo journalctl -u celery-worker -n 20 | grep Connected
... INFO/MainProcess] Connected to amqp://celery:**@127.0.0.1:5672/celery

Con RabbitMQ, las colas pendientes se consultan con sudo rabbitmqctl list_queues -p celery name messages.

Solución de problemas

  • Received unregistered task of type ...: el worker no ha importado el módulo de la tarea. Comprueba que está en include de la aplicación y reinicia el worker tras cambiar el código; los workers no recargan el código solos.
  • Las tareas se quedan en PENDING: ningún worker consume esa cola. Compara task_routes con las colas de -Q en cada worker.
  • Con Redis, una tarea larga se ejecuta dos veces: con task_acks_late, Redis vuelve a entregar las tareas no confirmadas tras el visibility_timeout, que por defecto es de 1 hora. Si tienes tareas más largas, súbelo con broker_transport_options={"visibility_timeout": 7200}.
  • Con RabbitMQ, el canal se cierra con PRECONDITION_FAILED y delivery acknowledgement timed out: una tarea tardó más que el consumer_timeout de RabbitMQ (30 minutos por defecto). Divide la tarea o aumenta ese valor en /etc/rabbitmq/rabbitmq.conf.

Conclusión

Tienes Celery funcionando en Ubuntu 24.04 con workers y Celery Beat gestionados por systemd, tareas repartidas en colas independientes, reintentos automáticos y monitorización con Flower, usando Redis o RabbitMQ como broker. Como siguientes pasos, puedes integrar Celery en tu aplicación Django o FastAPI, añadir límites de tiempo con task_time_limit para cortar tareas colgadas y repartir los workers en varios servidores apuntando al mismo broker.