How to Deploy a Django App to Production (Step by Step)
Taking a Django project from runserver to production: settings for DEBUG, ALLOWED_HOSTS and secrets, Postgres, collectstatic and WhiteNoise, Gunicorn behind Nginx or Caddy, systemd, migrations on deploy, HTTPS, and the checklist Django itself gives you.
python manage.py runserver is for development only — Django's own docs say not to use it in production. Deploying properly means a few changes to your settings and a couple of extra pieces around your app. (What is Django?)
The production setup
Browser → Nginx or Caddy (HTTPS, static files) → Gunicorn → Django → PostgreSQL
- Gunicorn — a production server that runs your Django app with several worker processes.
- Nginx or Caddy — a reverse proxy that handles HTTPS and passes requests to Gunicorn. (Reverse proxies explained)
- PostgreSQL — instead of the SQLite file used in development. (What is PostgreSQL?)
Step 1: production settings
Never deploy with development settings. Read sensitive values from environment variables:
# settings.py
import os
DEBUG = os.environ.get("DJANGO_DEBUG") == "1"
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",")
CSRF_TRUSTED_ORIGINS = ["https://example.com"]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
DEBUG = Falsein production. Debug pages show your code, settings and environment to anyone who triggers an error.SECRET_KEYmust be secret and not the one committed to Git. (Environment variables explained)ALLOWED_HOSTSmust list your domain(s), or Django returns 400 Bad Request.
Step 2: PostgreSQL
Install psycopg and configure DATABASES from a URL (the dj-database-url package makes this one line):
import dj_database_url
DATABASES = {"default": dj_database_url.config(conn_max_age=600)}
Set DATABASE_URL in the environment. (Postgres connection strings)
Step 3: static files
In production Django doesn't serve CSS and JS by itself. Collect them into one folder:
STATIC_ROOT = BASE_DIR / "staticfiles"
python manage.py collectstatic --noinput
Then either let Nginx/Caddy serve that folder, or add WhiteNoise so Gunicorn can serve them efficiently with no extra config. User uploads (MEDIA_ROOT) are different — store them somewhere persistent, ideally object storage. (Where should user uploads go?)
Step 4: dependencies
pip freeze > requirements.txt
Include django, gunicorn, psycopg[binary], dj-database-url, whitenoise. (requirements.txt explained)
Step 5: on the server
git clone https://github.com/you/mysite.git /srv/mysite
cd /srv/mysite
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --noinput
Step 6: run Gunicorn as a service
# /etc/systemd/system/mysite.service
[Unit]
Description=mysite Django
After=network.target
[Service]
User=app
WorkingDirectory=/srv/mysite
EnvironmentFile=/srv/mysite/.env
ExecStart=/srv/mysite/.venv/bin/gunicorn mysite.wsgi:application --bind 127.0.0.1:8000 --workers 3
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now mysite
Step 7: HTTPS with a reverse proxy
With Caddy, the whole config is:
example.com {
reverse_proxy 127.0.0.1:8000
}
(What is Caddy?) With Nginx, proxy to 127.0.0.1:8000 and get a certificate with Certbot. (Certbot and Let's Encrypt)
Step 8: Django's own checklist
python manage.py check --deploy
It warns about insecure settings — missing HSTS, cookies not marked secure, and more. Fix what applies.
On every deploy
git pull
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --noinput
sudo systemctl restart mysite
Automate it with a script or CI. (Deploy to a VPS with GitHub Actions)
Don't forget
- Backups of the database. (Postgres backup and restore)
- Background tasks (emails, reports) belong in a worker like Celery or RQ, not in the request. (Background jobs)
- Error monitoring. (Error monitoring for beginners)
EasySpawn runs Django the production way for you — Gunicorn behind HTTPS, Postgres with daily backups, migrations on deploy — and Claude Code can fix whatever check --deploy flags. See how it works or join the waitlist.
Related: What Is Django? · Flask vs Django vs FastAPI · How to Deploy a Flask App · How to Secure a New VPS
Keep reading
What Is Vercel? What It Does, What It Costs You, and When to Use Something Else
Vercel is a hosting platform built around frontend frameworks, especially Next.js: push to GitHub and your site is live. What it actually does, what serverless functions mean for your app, where the limits are, and when a regular server is a better fit.
What Is Nginx? The Web Server in Front of Half the Internet
Nginx ("engine-x") is a web server that serves files, forwards requests to your app, handles HTTPS and balances load. What it does, what a basic config looks like, where the files live, the commands you'll need, and whether you need it at all.