Django is a high-level Python web framework that follows the “batteries included” philosophy — it provides an ORM, authentication, admin interface, form validation, URL routing, templating, and security middleware out of the box. This makes Django the framework of choice for data-driven applications and content management systems where rapid development and maintainability matter. Django’s ORM supports PostgreSQL, MySQL, SQLite, and Oracle, and its built-in admin interface auto-generates a management UI from model definitions. For production deployment, Django applications are typically served via Gunicorn (a WSGI server) behind Nginx, which handles SSL termination and static file serving. This guide covers deploying a Django 5 application with Gunicorn and Nginx on RHEL 9 with a PostgreSQL database backend.
Prerequisites
- Python 3.12 installed on RHEL 9
- Nginx and PostgreSQL 16 installed
Step 1 — Create the Django Project
mkdir /var/www/mydjango && cd /var/www/mydjango
python3.12 -m venv .venv
source .venv/bin/activate
pip install django gunicorn psycopg2-binary python-dotenv whitenoise
django-admin startproject mysite .
python manage.py startapp core
Step 2 — Configure settings.py for Production
# mysite/settings.py — production settings
from dotenv import load_dotenv
import os
load_dotenv()
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
DEBUG = False
ALLOWED_HOSTS = ['mydjango.example.com']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME', 'mydjango_db'),
'USER': os.environ.get('DB_USER', 'mydjango_user'),
'PASSWORD': os.environ['DB_PASSWORD'],
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
# Serve static files via WhiteNoise (production)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
...
]
STATIC_ROOT = '/var/www/mydjango/staticfiles'
STATIC_URL = '/static/'
STORAGES = {"staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"}}
Step 3 — Prepare the Application
cd /var/www/mydjango
# Collect static files
python manage.py collectstatic --noinput
# Run migrations
python manage.py migrate
# Create superuser for admin
python manage.py createsuperuser
Step 4 — Create Gunicorn systemd Service
# /etc/systemd/system/mydjango.service
[Unit]
Description=Django/Gunicorn for mydjango
After=network.target
[Service]
User=nginx
Group=nginx
WorkingDirectory=/var/www/mydjango
EnvironmentFile=/var/www/mydjango/.env
ExecStart=/var/www/mydjango/.venv/bin/gunicorn
--workers 4
--bind unix:/run/mydjango.sock
--access-logfile /var/log/mydjango/access.log
--error-logfile /var/log/mydjango/error.log
mysite.wsgi:application
Restart=always
[Install]
WantedBy=multi-user.target
mkdir -p /var/log/mydjango
chown nginx:nginx /var/log/mydjango
systemctl daemon-reload
systemctl enable --now mydjango
Step 5 — Configure Nginx Reverse Proxy
# /etc/nginx/conf.d/mydjango.conf
server {
listen 80;
server_name mydjango.example.com;
location /static/ {
alias /var/www/mydjango/staticfiles/;
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
proxy_pass http://unix:/run/mydjango.sock;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
nginx -t && systemctl reload nginx
Conclusion
Django with Gunicorn and Nginx on RHEL 9 is the standard production deployment stack. Gunicorn’s worker count should be set to 2 × CPU_CORES + 1 for CPU-bound applications, or higher for I/O-bound applications that spend most of their time waiting on database queries. The WhiteNoise middleware eliminates the need to configure Nginx to serve static files separately and is production-safe with compressed, cache-optimised static file delivery built in.
Next steps: How to Deploy a Flask Application on RHEL 9, How to Install Python 3 on RHEL 9, and How to Configure PostgreSQL on RHEL 9.