Skip to content

Celery Worker Shutdown: Graceful Worker Termination and Warm Shutdown Strategies

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Celery Worker Shutdown: Graceful Worker Termination and Warm Shutdown Strategies. We cover key concepts, practical examples, and best practices to help you master this topic.

Celery worker shutdown strategies control how workers terminate -- graceful warm shutdown completes in-progress tasks before exiting, while cold shutdown immediately terminates processes, enabling deployment strategies that minimize task loss and service disruption.

flowchart LR
    S[Shutdown Signal] --> W{Shutdown Mode}
    W -->|Warm| Wait[Wait for Tasks]
    Wait -->|All Complete| Exit[Exit Gracefully]
    W -->|Cold| Kill[Kill Processes]
    Kill --> DataLoss[Potential Data Loss]
    Wait -->|Timeout| Force[Force Exit]
    S --> Hook[Shutdown Hooks]
    Hook --> Cleanup[Close Connections]
    Hook --> Notify[Notify Monitoring]

What You'll Learn

  • Warm vs cold shutdown behavior
  • Shutdown signals and handling
  • Shutdown hooks for cleanup
  • Zero-downtime worker replacement
  • Handling in-flight tasks during shutdown

Why It Matters

Killing workers without graceful shutdown loses in-progress tasks, leaves database connections open, and disrupts monitoring. Warm shutdown ensures all started tasks complete, while shutdown hooks properly release resources and notify external systems.

Real-World Use

DodaTech's Celery cluster undergoes rolling deployments every week. Warm shutdown on each worker ensures the 200+ in-flight malware scan tasks complete before the worker exits, preventing file resubmissions and scan result loss.

Warm Shutdown

from celery import Celery
import signal
import time

app = Celery('shutdown', broker='redis://localhost:6379/0')
app.conf.task_acks_late = True

@app.task(bind=True)
def critical_task(self, item_id):
    for i in range(10):
        time.sleep(0.5)
        print(f"Task {item_id}: step {i + 1}/10")
        self.update_state(state='PROGRESS', meta={'step': i + 1, 'total': 10})
    print(f"Task {item_id}: completed")
    return f"Item {item_id} processed"

print("Worker running. Send SIGTERM for warm shutdown.")

Send warm shutdown:

kill -SIGTERM <worker_pid>

Expected output:

Worker running. Send SIGTERM for warm shutdown.
[2026-06-28 10:00:00: INFO] Warm shutdown requested (SIGTERM)
Task item-01: step 1/10
...
Task item-01: step 10/10
Task item-01: completed
[2026-06-28 10:00:05: INFO] All tasks complete, shutting down
[2026-06-28 10:00:05: INFO] Worker stopped gracefully

Shutdown Hooks

from celery import Celery
from celery.signals import worker_shutting_down
import time

app = Celery('hooks', broker='redis://localhost:6379/0')

db_pool = {"connections": 5}
monitoring_active = True

@worker_shutting_down.connect
def handle_shutdown(sender, **kwargs):
    print("Shutdown hook triggered")
    print(f"Closing {db_pool['connections']} database connections...")
    db_pool['connections'] = 0
    print("Database connections closed")

    global monitoring_active
    monitoring_active = False
    print("Monitoring deactivated")

    print("Worker shutdown complete")

@app.task
def monitored_task(task_id):
    if not monitoring_active:
        print(f"Task {task_id}: shutdown in progress, completing anyway")
    time.sleep(1)
    result = f"Task {task_id} done"
    print(result)
    return result

for i in range(3):
    monitored_task.delay(f"M-{i}")
print("Tasks submitted. Worker will complete them on shutdown.")

Expected output:

Tasks submitted. Worker will complete them on shutdown.
Task M-0 done
Task M-1 done
Task M-2 done
[Worker receives SIGTERM]
Shutdown hook triggered
Closing 5 database connections...
Database connections closed
Monitoring deactivated
Worker shutdown complete

Zero-Downtime Replacement

from celery import Celery
import time

app = Celery('zero_downtime', broker='redis://localhost:6379/0')

@app.task(bind=True)
def steady_task(self, task_num):
    for i in range(5):
        time.sleep(0.5)
        print(f"Task {task_num}: working...")
    print(f"Task {task_num}: finished")
    return task_num

print("Deployment strategy:")
print("1. Start new worker (new deployment)")
print("2. Wait for it to connect and start consuming")
print("3. Send SIGUSR1 to old worker (warm shutdown)")
print("4. Old worker finishes current tasks, stops taking new ones")
print("5. New worker handles all tasks seamlessly")

Expected output:

Deployment strategy:
1. Start new worker (new deployment)
2. Wait for it to connect and start consuming
3. Send SIGUSR1 to old worker (warm shutdown)
4. Old worker finishes current tasks, stops taking new ones
5. New worker handles all tasks seamlessly

Shutdown Timeout

from celery import Celery
import time

app = Celery('timeout', broker='redis://localhost:6379/0')

app.conf.worker_shutdown_timeout = 30

@app.task(bind=True)
def slow_cleanup(self, task_id):
    try:
        for i in range(100):
            time.sleep(1)
            print(f"Task {task_id}: step {i + 1}")
        return "Completed"
    except Exception:
        print(f"Task {task_id}: interrupted during cleanup")
        return "Interrupted"

task = slow_cleanup.delay("long-job")
print(f"Submitted: {task.id}")
print("Worker will wait 30 seconds before force-killing remaining tasks")

Expected output:

Submitted: id
Worker will wait 30 seconds before force-killing remaining tasks
Task long-job: step 1
...
Task long-job: step 30
[2026-06-28 10:00:30: WARNING] Shutdown timeout expired (30s)
[2026-06-28 10:00:30: WARNING] Force killing remaining tasks

Common Mistakes

  • Using SIGKILL for shutdown -- SIGKILL cannot be caught, so warm shutdown and hooks are bypassed. Always use SIGTERM for graceful shutdown. Use SIGKILL only when a worker is completely stuck.
  • Not setting shutdown timeout -- without worker_shutdown_timeout, workers wait indefinitely for tasks to finish. Set a reasonable timeout (e.g., 300 seconds) to prevent deployments from hanging.
  • Forgetting to handle in-flight tasks on shutdown -- tasks running during shutdown need special handling. Use task_acks_late so tasks can be redelivered if the worker terminates before completion.
  • Shutdown hooks that block -- if a shutdown hook takes too long, it delays the shutdown. Keep hooks fast or run cleanup in a separate thread with its own timeout.
  • Not draining queues before shutdown -- when a worker shuts down, it stops consuming. Tasks remain in the queue for other workers. Ensure at least one worker remains active to prevent queue buildup.

Practice Questions

  1. What is the difference between warm and cold worker shutdown?
  2. How do you trigger a warm shutdown in Celery?
  3. Why should you set worker_shutdown_timeout?
  4. What happens to unacknowledged tasks during a forced shutdown?
  5. How do you implement zero-downtime worker replacement?

Challenge

Build a deployment pipeline for Celery workers that: (1) deploys a new version of a worker, (2) sends warm shutdown to the old worker, (3) monitors the old worker's in-flight tasks, (4) implements a fallback that force-kills the old worker after 60 seconds, (5) verifies zero task loss by comparing task completion counts before and after deployment, and (6) rolls back if the new worker fails health checks.

FAQ

What happens to running tasks when a worker is shut down?

With warm shutdown (SIGTERM), running tasks complete before the worker exits. With cold shutdown (SIGKILL), running tasks are terminated. If task_acks_late is enabled, unacknowledged tasks are redelivered to other workers.

What is the difference between SIGTERM and SIGUSR1 for Celery workers?

SIGTERM initiates warm shutdown with task completion. SIGUSR1 tells the worker to stop accepting new tasks but continue running current ones until they finish, then exit. SIGUSR1 is useful for gradual draining.

Can I customize shutdown behavior?

Yes. Connect to the worker_shutting_down signal to add cleanup logic. Override worker_shutdown_timeout to control how long the worker waits for in-flight tasks before force-exiting.

How do I ensure zero task loss during a deployment?

Use warm shutdown (SIGTERM or SIGUSR1) with task_acks_late=True. Start new workers before shutting down old ones. Ensure the broker is configured with visibility timeout for redelivery.

Does shutdown affect periodic tasks?

Yes. When a worker shuts down, its Celery Beat scheduler stops. Periodic tasks are not executed during shutdown. When the worker restarts, the scheduler resumes and may run missed tasks depending on configuration.

Mini Project

Build a worker lifecycle manager that: (1) maintains a pool of Celery workers with health checks every 10 seconds, (2) performs rolling restarts by gracefully shutting down one worker at a time, (3) monitors shutdown progress and escalates to SIGKILL after timeout, (4) tracks task redelivery rate during restarts as a quality metric, and (5) sends Slack notifications when a worker fails to shut down gracefully.

What's Next

Continue with Remote Control to learn how to manage workers programmatically. Then explore Broadcast Messages for sending commands to all workers simultaneously.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro