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 llamayour_user; sustitúyelo por el tuyo. - Conocimientos básicos de Python 3.
Cómo encajan las piezas
| Componente | Funció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 |
| Worker | Proceso de Celery que ejecuta las tareas |
| Backend de resultados | Guarda el valor devuelto por cada tarea para consultarlo después (opcional) |
| Celery Beat | Planificador 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
Importanteejecuta una sola instancia de Celery Beat en toda la infraestructura. Si arrancas dos, cada tarea periódica se encolará dos veces.
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á enincludede 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. Comparatask_routescon las colas de-Qen cada worker. - Con Redis, una tarea larga se ejecuta dos veces: con
task_acks_late, Redis vuelve a entregar las tareas no confirmadas tras elvisibility_timeout, que por defecto es de 1 hora. Si tienes tareas más largas, súbelo conbroker_transport_options={"visibility_timeout": 7200}. - Con RabbitMQ, el canal se cierra con
PRECONDITION_FAILEDydelivery acknowledgement timed out: una tarea tardó más que elconsumer_timeoutde 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.
