How to configure Celery to run as systemd service with a Django application served by Gunicorn?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Celery should run as its own supervised service, even when the Django web application is already running under Gunicorn. Gunicorn serves HTTP requests, while Celery workers consume background jobs from the broker, so systemd should manage them as separate processes with separate unit files.
Keep Gunicorn and Celery as Independent Services
One common misunderstanding is that Celery somehow runs "inside" Gunicorn because both processes import the same Django project. In production they should be treated as peers:
- Gunicorn runs the WSGI or ASGI app.
- Celery worker processes background tasks.
- Celery Beat, if used, schedules recurring tasks.
That separation is useful operationally. You can restart a web service without interrupting background work, or scale workers without touching the HTTP stack.
Before writing any unit file, confirm the Django project exposes a proper Celery application.
If celery -A myproject inspect ping fails in the virtual environment, fix that first. systemd should supervise a working command, not hide an import problem.
Create a Dedicated Worker Unit
For modern Celery deployments, Type=simple is usually the correct systemd setting. Let Celery stay in the foreground and allow systemd to monitor the main process directly.
Save that as /etc/systemd/system/celery.service. Then reload and start it:
This gives you automatic startup on boot, restart on failure, and a clear process boundary.
Keep Gunicorn in Its Own Unit File
Gunicorn should have a separate service with the same project directory and virtual environment, but a different startup command.
The two services are related by codebase, not by process ownership. A deploy should typically restart both because the shared application code changed, but a crash in one does not mean the other should be bundled into the same service definition.
Put Environment Values in an Environment File
Once broker URLs, secrets, and Django settings become nontrivial, a separate environment file keeps the unit readable.
Example environment file:
This also makes secret rotation and environment-specific overrides easier to manage than editing the unit file itself.
Run Beat Separately if You Need Scheduling
If the project uses scheduled jobs, Celery Beat should be a second service rather than a hidden child process of the worker.
Operationally this is cleaner. If Beat fails, worker throughput is unaffected. If workers fail, scheduling can still be diagnosed separately.
Logs from both services are available through the journal:
Common Pitfalls
- Assuming Gunicorn starts Celery automatically because both use the same Django project.
- Using
Type=forkingwhen Celery can run in the foreground and be supervised directly. - Skipping manual command-line verification before blaming
systemdfor startup failures. - Mixing worker and Beat into one hard-to-debug service definition.
- Forgetting
WorkingDirectory,PATH, orDJANGO_SETTINGS_MODULE, which causes most import failures.
Summary
- Run Gunicorn, Celery worker, and Celery Beat as separate
systemdservices. - Verify the Django and Celery wiring before writing unit files.
- Use
Type=simpleand keep Celery in the foreground. - Store environment-specific values in an
EnvironmentFile. - Use
journalctlandsystemctl statusfor routine supervision and debugging.

