Running Django in production means a long-running Python process behind a web server, not the runserver command you use while developing. The usual stack on your own server is Gunicorn for the app, nginx in front, PostgreSQL for data and systemd to keep it all running. This checklist walks through that setup on a Linux VPS, the settings to harden before launch, and the mistakes that most often take Django sites down.
On a VPS: create a non-root user, a PostgreSQL database and a virtual environment; set DEBUG = False, ALLOWED_HOSTS and a secret key from the environment; run python manage.py check --deploy; serve the app with Gunicorn under systemd, bound to 127.0.0.1; put nginx in front for static files and HTTPS with Let's Encrypt. Use Django 5.2 LTS for new projects. A small Django site can also run on Domain India cPanel or DirectAdmin hosting through the Python app tool, and the App Platform runs Django from a Dockerfile.
1. Choose where Django runs
Django doesn't need a VPS in every case. Pick by what the project needs:
| Where | Good for | Limits |
|---|---|---|
| Shared hosting (cPanel or DirectAdmin Python app tool) | Small Django sites next to an existing website | WSGI only, shared CPU and memory, no background workers, no root |
| App Platform | Production apps without server admin | You provide a Dockerfile; no SSH or root |
| VPS | Full control: Gunicorn, Celery, Channels, any system package | You run and secure the server yourself |
For the shared hosting route, follow how to deploy a Python app on shared hosting. The rest of this guide is for a VPS or server where you have root.
2. Prepare the server
Use a current long-term-support release such as Ubuntu 24.04 LTS, Debian 12 or AlmaLinux 9, with Python 3.12 or later. On Ubuntu or Debian:
sudo apt update
sudo apt install python3 python3-venv python3-pip \
postgresql nginx git \
certbot python3-certbot-nginxOn AlmaLinux use dnf with the equivalent packages. Then create an unprivileged user for the app. Never run Django as root:
sudo adduser --system --group --home /home/django --shell /bin/bash django3. Create the database
sudo -u postgres psql <<'EOF'
CREATE USER myapp WITH PASSWORD 'use-a-long-random-password';
CREATE DATABASE myapp_production OWNER myapp;
EOFMaking the app user the database owner gives it the rights it needs on PostgreSQL 15 and later, where ordinary users can no longer create tables in the public schema by default. Keep PostgreSQL listening on localhost only.
4. Code, virtual environment and requirements
As the django user, clone your project and create a virtual environment:
sudo -iu django
git clone https://github.com/you/myapp.git
cd myapp
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txtA minimal requirements.txt for production:
Django>=5.2,<5.3
gunicorn
psycopg[binary]
whitenoise
python-decoupleDjango 5.2 is the current long-term-support release, with security fixes until April 2028. Django 4.2 LTS reached end of support in April 2026, so upgrade any project still on it.
5. Harden settings.py
The default settings.py is for development. Read secrets from the environment (here with python-decouple, which reads a .env file) and never commit them.
from decouple import config, Csv
DEBUG = config("DEBUG", default=False, cast=bool)
SECRET_KEY = config("SECRET_KEY")
ALLOWED_HOSTS = config("ALLOWED_HOSTS", cast=Csv())
CSRF_TRUSTED_ORIGINS = ["https://yourdomain.com", "https://www.yourdomain.com"]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": config("DB_NAME"),
"USER": config("DB_USER"),
"PASSWORD": config("DB_PASSWORD"),
"HOST": config("DB_HOST", default="localhost"),
"PORT": config("DB_PORT", default="5432"),
"CONN_MAX_AGE": 60,
"CONN_HEALTH_CHECKS": True,
}
}
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 3600 # raise to 31536000 once HTTPS is proven
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}Generate a fresh secret key for production and put it, with the other values, in /home/django/myapp/.env, readable only by the django user:
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"If you use WhiteNoise, add whitenoise.middleware.WhiteNoiseMiddleware directly after SecurityMiddleware in MIDDLEWARE. The old STATICFILES_STORAGE setting was removed in Django 5.1; use STORAGES as above.
HSTS tells browsers to refuse plain HTTP for your domain for the whole period you set. Start with a short value, confirm every page and subdomain works over HTTPS, then raise it to a year. Add SECURE_HSTS_INCLUDE_SUBDOMAINS and SECURE_HSTS_PRELOAD only when every subdomain has a valid certificate, because preloading is hard to undo. See security headers explained.
6. Migrate, collect static files and run the deploy check
With the virtual environment active:
python manage.py check --deploy
python manage.py migrate
python manage.py collectstatic --noinput
python manage.py createsuperusercheck --deploy is Django's own pre-launch audit. Fix every warning it prints before you continue.
7. Run Gunicorn under systemd
Test Gunicorn by hand first: gunicorn myapp.wsgi --bind 127.0.0.1:8000. Then create /home/django/myapp/gunicorn.conf.py:
import multiprocessing
bind = "127.0.0.1:8000"
workers = multiprocessing.cpu_count() * 2 + 1
timeout = 60
max_requests = 1000
max_requests_jitter = 50
accesslog = "-"
errorlog = "-"Size workers to your memory as well as your CPU: each worker holds a full copy of your app. Then create /etc/systemd/system/myapp.service as root:
[Unit]
Description=Gunicorn for myapp
After=network.target postgresql.service
[Service]
User=django
Group=django
WorkingDirectory=/home/django/myapp
EnvironmentFile=/home/django/myapp/.env
ExecStart=/home/django/myapp/venv/bin/gunicorn --config gunicorn.conf.py myapp.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp
journalctl -u myapp -f8. nginx in front, then HTTPS
Create an nginx site for your domain (/etc/nginx/sites-available/myapp.conf on Ubuntu and Debian, /etc/nginx/conf.d/myapp.conf on AlmaLinux):
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
client_max_body_size 20m;
location /static/ {
alias /home/django/myapp/staticfiles/;
expires 30d;
}
location /media/ {
alias /home/django/myapp/media/;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
}
}nginx must be able to read the static folder: give it execute permission on /home/django or move static files under /var/www. Test and reload with sudo nginx -t && sudo systemctl reload nginx, then get a certificate:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
sudo certbot renew --dry-runPoint the domain's A record at the VPS before running Certbot. See how to change your domain's DNS settings.
9. Deploy updates
A simple deploy, run as the django user:
cd ~/myapp
git pull
source venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --noinput
sudo systemctl reload myappThe last line needs a sudo rule that lets the django user reload only this service. To automate the same steps from GitHub, adapt deploying with GitHub Actions to SSH into the VPS and reload systemd.
10. Pre-launch checklist
- Deploy check is clean.
python manage.py check --deployprints no warnings. - Secrets are out of the code.
DEBUGis off, andSECRET_KEYand database passwords live only in.env. - Hosts are explicit.
ALLOWED_HOSTSandCSRF_TRUSTED_ORIGINSlist your real domains, never*. - Gunicorn is private.It listens on
127.0.0.1only, and the firewall allows just SSH, 80 and 443. - HTTPS works.HTTP redirects to HTTPS and
curl -I https://yourdomain.comshows the security headers. - Static and media files load.No 404s for CSS, JavaScript or uploads.
- Backups run.Database dumps and the media folder are copied off the server; see automated backups with cron and rclone.
- Errors reach you.Logs are rotated and an error tracker such as Sentry, or at least email to
ADMINS, is set up.
11. Common mistakes
12. Running Django on Domain India
VPS. Our VPS plans are self-managed with full root access, so everything in this guide applies. You install and patch the software and look after backups yourself.
- 1 vCPU
- 2 GB DDR4 RAM
- 64 GB NVMe SSD Storage
- 2 TB Monthly Bandwidth
App Platform. If you would rather not run a server, the App Platform runs Django from your own Dockerfile (only Node.js is detected automatically), deploys from GitHub or with a deploy token, and includes a PostgreSQL database. It does not support WebSockets, so Django Channels needs a VPS. See getting started with the App Platform.
- 512 MB RAM per app
- 1 vCPU
- 5 GB NVMe SSD
- PostgreSQL Database
Shared hosting. Our cPanel and DirectAdmin servers can run a small Django site as a WSGI app through the panel's Python app tool, with a MySQL-compatible database. Jailed SSH access is available on every shared hosting plan (cPanel, DirectAdmin, Webuzo). It is off by default; ask support to enable it for your account.
Can I use SQLite for Django in production?
For a small, mostly read-only site it can work. SQLite allows one writer at a time, so for anything with regular writes or several users at once, use PostgreSQL or MySQL.
Which Django version should I use in 2026?
Django 5.2, the current long-term-support release, which receives security fixes until April 2028. Django 4.2 reached end of support in April 2026.
Gunicorn or uWSGI?
Gunicorn is simpler and fits most Django apps. uWSGI offers more options, but most projects don't need them, and its development has slowed.
Do I need Docker to deploy Django?
No. systemd with a virtual environment is enough for one app on one server. Docker helps when you run several apps, need identical environments, or deploy to the App Platform, which builds from a Dockerfile.
How do I run Django with WebSockets?
Django Channels needs an ASGI server such as Daphne or Uvicorn behind nginx instead of plain Gunicorn. Run it on a VPS; the Domain India App Platform does not support WebSockets.
Can I run Django on Domain India shared hosting?
Yes, for small sites. Our cPanel and DirectAdmin servers have a Python app tool that serves Django as a WSGI app through a passenger_wsgi.py file. Background workers and WebSockets need a VPS.
How do I run scheduled Django commands?
On a VPS, use a cron job or a systemd timer that activates the virtual environment and runs python manage.py yourcommand. See the cron expression guide for the schedule syntax.
Ready to deploy? Compare VPS plans, try the App Platform, or read the Django, FastAPI and Flask comparison if you are still choosing a framework. Schedule jobs with the cron expression guide.
Self-managed VPS plans with full root access, ready for Gunicorn, nginx and PostgreSQL.
See VPS plans