python manage.py runserver 0.0.0.0:8000 exposed on the Internet with DEBUG=True. SQLite in production. Uploads in the repo. Three mistakes that turn a promising Django project into a data breach waiting to happen. The minimum path to a clean deployment is well known — it just takes not cutting corners out of fatigue.
Django in production is not more complex than another web framework. It is mostly discipline: separate environments, externalise files, and never confuse what worked locally with what is acceptable in public.
Minimum path in seven steps
| Step | Command / action | Frequent omission |
|---|---|---|
| Prod settings | settings/production.py, DEBUG=False | Committed secret key |
| Database | PostgreSQL or MySQL | SQLite in prod |
| Static | collectstatic → nginx/CDN | Static served by Django |
| Media | Object storage or dedicated volume | Media in git |
| WSGI | Gunicorn + socket or port | runserver |
| Proxy | nginx TLS + headers | No HTTPS redirect |
| Migrations | migrate with backup | migrate without rollback test |
Django in production is mostly stopping what was convenient locally.
Typical stack
Internet → nginx (TLS, static) → Gunicorn (workers) → Django
↓
PostgreSQL
↓
Redis (cache/Celery optional)
Gunicorn workers: (2 × CPU) + 1 as a starting rule — adjust for memory and heavy requests.
Variables and secrets
Set DJANGO_SETTINGS_MODULE to production config. Use a unique SECRET_KEY per environment. List ALLOWED_HOSTS explicitly — no * wildcard. Configure email backend (SMTP or transactional API). Add CSRF_TRUSTED_ORIGINS for multiple domains. Inject vars via host or file outside repo.
Celery and async tasks
If you send email, run exports or heavy webhooks, plan Redis or RabbitMQ broker, supervised worker, failed task monitoring, and separate web and batch workers if load requires it.
The climax: Django prod is boring by design
See Hosting Node.js for long-process culture — proxy plus workers principles are similar.
Decide and move forward without blind spots
Create a separate prod settings module and forbid DEBUG=True in production. Move to PostgreSQL with regularly tested backups. Configure nginx plus Gunicorn under systemd or equivalent. Integrate collectstatic in the deploy pipeline. Monitor 5xx errors and media disk space. Browse Python hosts in the directory.
Frequently asked questions
Can Django run without Gunicorn?
Not in prod — Gunicorn/uWSGI/ASGI behind proxy.
Where to serve static and media?
Static nginx/CDN after collectstatic; media object storage or backed volume.
Do you need Celery from day one?
From async tasks — otherwise defer with Redis planned.
Which host for a first Django app?
Python PaaS or VPS with managed PostgreSQL. Avoid shared without WSGI control.
Production Django is not recognised by its URL — it is recognised by the absence of runserver in ps aux.