← Back to all blogs
Django Production Setup - Complete Guide
Sat Feb 28 20268 minIntermediate

Django Production Setup - Complete Guide

A comprehensive, SEO‑optimized guide that walks you through every step required to launch a Django application in a production environment.

#django#production#deployment#gunicorn#nginx#docker#aws#security

Understanding Django Production Architecture

<h2>Why a Dedicated Production Architecture Matters</h2> <p>Running Django in development mode is convenient, but it is neither performant nor secure for real users. A production architecture isolates concerns, enables horizontal scaling, and provides a clear path for monitoring, logging, and disaster recovery.</p> <h3>Typical Production Stack</h3> <p>The most common stack includes:</p> <ul> <li><strong>Web Server (Nginx or Apache)</strong> - Handles TLS termination, static & media files, and acts as a reverse proxy.</li> <li><strong>Application Server (Gunicorn, uWSGI, or Daphne)</strong> - Executes Python code in a WSGI/ASGI environment.</li> <li><strong>Database (PostgreSQL, MySQL, or managed cloud service)</strong> - Persists relational data.</li> <li><strong>Cache Layer (Redis or Memcached)</strong> - Stores session data, query results, and Celery task queues.</li> <li><strong>Message Broker (RabbitMQ or Redis)</strong> - Enables asynchronous background jobs with Celery.</li> <li><strong>Object Storage (Amazon S3, Google Cloud Storage)</strong> - Serves user‑uploaded media.</li> </ul> <p>Below is a high‑level diagram of this architecture:</p> <pre><code>+-------------------+ +-------------------+ +--------------------+ | Client Browser | ---> | Nginx (TLS) | ---> | Gunicorn Workers | +-------------------+ +-------------------+ +--------------------+ | v +--------------+ | Django App | +--------------+ | +-----------+-----------+------------+-----------+-----------+ | | | | | | v v v v v v PostgreSQL Redis Cache Celery (RabbitMQ) S3 Bucket ElasticSearch </code></pre> <p>This separation ensures each component can be tuned independently, supports zero‑downtime deployments, and simplifies troubleshooting.</p> <h3>Dockerizing the Stack (Optional)</h3> <p>Containerization is not mandatory, but Docker dramatically reduces environment drift. A typical <code>docker‑compose.yml</code> would define services for <code>web</code> (Nginx), <code>app</code> (Gunicorn), <code>db</code> (PostgreSQL), <code>redis</code>, and <code>celery</code>. The following snippet illustrates the core services:</p> <pre><code>version: "3.9" services: nginx: image: nginx:stable-alpine ports: - "80:80" - "443:443" volumes: - ./nginx/conf:/etc/nginx/conf.d:ro - static_volume:/app/static - media_volume:/app/media depends_on: - app

app: build: . command: gunicorn myproject.wsgi:application --workers 4 --bind 0.0.0.0:8000 env_file: .env volumes: - .:/app expose: - "8000" depends_on: - db - redis

db: image: postgres:14-alpine environment: POSTGRES_DB: mydb POSTGRES_USER: myuser POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data

redis: image: redis:6-alpine volumes: - redis_data:/data

volumes: static_volume: media_volume: postgres_data: redis_data: </code></pre>

<p>Even if you choose a VM‑based deployment, the logical separation described above remains the same.</p>

Configuring the Server Environment

<h2>Step‑by‑Step Server Configuration</h2> <p>Below are the essential tasks you must complete before your Django app can serve traffic safely.</p> <h3>1. Install System Packages</h3> <p>On Ubuntu 22.04, the following command installs Nginx, Python 3.11, virtualenv, and supporting libraries:</p> <pre><code>sudo apt update && sudo apt install -y \ nginx python3-pip python3-venv build-essential libpq-dev </code></pre> <h3>2. Create a Dedicated System User</h3> <p>Running the application under a non‑root user limits the impact of a potential breach.</p> <pre><code>sudo adduser --system --group --home /opt/django_app django_user sudo mkdir -p /opt/django_app/{logs,run,static,media} sudo chown -R django_user:django_user /opt/django_app </code></pre> <h3>3. Set Up a Python Virtual Environment</h3> <p>Isolate project dependencies from the global interpreter.</p> <pre><code>sudo -u django_user python3 -m venv /opt/django_app/venv source /opt/django_app/venv/bin/activate pip install --upgrade pip pip install django gunicorn psycopg2-binary celery redis </code></pre> <h3>4. Configure Django Settings for Production</h3> <p>Keep secrets out of version control by using environment variables. A minimal <code>settings_production.py</code> might look like:</p> <pre><code>import os from .settings import *

DEBUG = False ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', 'example.com').split(',')

Database - use PostgreSQL via environment variables

DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': os.getenv('POSTGRES_DB'), 'USER': os.getenv('POSTGRES_USER'), 'PASSWORD': os.getenv('POSTGRES_PASSWORD'), 'HOST': os.getenv('POSTGRES_HOST', 'localhost'), 'PORT': os.getenv('POSTGRES_PORT', '5432'), } }

Static & Media files - served by Nginx

STATIC_ROOT = os.path.join(BASE_DIR, 'static/') MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

Security settings

SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True X_FRAME_OPTIONS = 'DENY'

Logging - JSON for easy ingestion by ELK or Loki

LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'json': { '()': 'pythonjsonlogger.jsonlogger.JsonFormatter', 'fmt': '%(asctime)s %(levelname)s %(name)s %(message)s', }, }, 'handlers': { 'file': { 'class': 'logging.FileHandler', 'filename': '/opt/django_app/logs/django.json', 'formatter': 'json', }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'), }, }, } </code></pre>

<p>Export the path to this settings module when starting Gunicorn:</p> <pre><code>export DJANGO_SETTINGS_MODULE=myproject.settings_production </code></pre> <h3>5. Deploy Gunicorn as a Systemd Service</h3> <p>Systemd monitors the process, restarts it on failure, and integrates with logrotate.</p> <pre><code>[Unit] Description=Gunicorn daemon for Django project After=network.target

[Service] User=django_user Group=django_user WorkingDirectory=/opt/django_app Environment="PATH=/opt/django_app/venv/bin" ExecStart=/opt/django_app/venv/bin/gunicorn myproject.wsgi:application
--workers 4
--bind unix:/opt/django_app/run/gunicorn.sock Restart=on-failure

[Install] WantedBy=multi-user.target </code></pre>

<p>Enable and start the service:</p> <pre><code>sudo systemctl daemon-reload sudo systemctl enable gunicorn sudo systemctl start gunicorn </code></pre> <h3>6. Configure Nginx as a Reverse Proxy</h3> <p>Create <code>/etc/nginx/sites-available/django.conf</code> and enable it.</p> <pre><code>server { listen 80; server_name example.com www.example.com;
# Enforce HTTPS - let Certbot handle certs later
return 301 https://$host$request_uri;

}

server { listen 443 ssl http2; server_name example.com www.example.com;

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

# Static files
location /static/ {
    alias /opt/django_app/static/;
    expires 30d;
    add_header Cache-Control "public, max-age=2592000";
}
location /media/ {
    alias /opt/django_app/media/;
}

# Proxy to Gunicorn via Unix socket
location / {
    proxy_pass http://unix:/opt/django_app/run/gunicorn.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;
}

} </code></pre>

<p>Enable the site and reload Nginx:</p> <pre><code>sudo ln -s /etc/nginx/sites-available/django.conf /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx </code></pre> <h3>7. Obtain and Auto‑Renew TLS Certificates</h3> <p>Let’s Encrypt provides free certificates. Install Certbot and run:</p> <pre><code>sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d example.com -d www.example.com </code></pre> <p>The tool automatically updates the Nginx config and sets up a systemd timer for renewal.</p>

Security, Scaling, and Monitoring Best Practices

<h2>Hardening Your Django Production Environment</h2> <p>Security is non‑negotiable. Follow these practices to protect data, users, and infrastructure.</p> <h3>Secret Management</h3> <ul> <li>Never hard‑code <code>SECRET_KEY</code> or database credentials.</li> <li>Use a secret manager (AWS Secrets Manager, HashiCorp Vault, or Docker secrets) and load values at runtime.</li> <li>Rotate secrets regularly; Django reads them from <code>os.getenv</code> each start.</li> </ul> <h3>Content Security Policy (CSP)</h3> <p>Add a CSP header via Nginx or Django‑CSP to mitigate XSS attacks.</p> <pre><code># In Nginx add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net"; </code></pre> <h3>Database Hardening</h3> <p>Configure PostgreSQL with <code>sslmode=require</code>, limit connections, and enable <code>pgcrypto</code> for encrypted columns.</p> <h3>Scaling Techniques</h3> <p>When traffic grows, you can scale horizontally without code changes.</p> <ul> <li><strong>Gunicorn Workers</strong>: Increase <code>--workers</code> based on <code>(2 × CPU) + 1</code> formula.</li> <li><strong>Load Balancer</strong>: Place an AWS ELB or Google Cloud Load Balancer in front of multiple Nginx instances.</li> <li><strong>Cache Layer</strong>: Move session storage to Redis and enable Django's per‑view caching:</li> </ul> <pre><code>from django.views.decorators.cache import cache_page

@cache_page(60 * 15) # Cache for 15 minutes def homepage(request): return render(request, 'home.html') </code></pre>

<ul> <li><strong>Asynchronous Tasks</strong>: Offload long‑running jobs to Celery workers that consume messages from RabbitMQ or Redis.</li> </ul> <h3>Observability Stack</h3> <p>Collect logs, metrics, and traces to detect anomalies early.</p> <ul> <li><strong>Logging</strong>: Ship <code>/opt/django_app/logs/*.json</code> to Loki or Elastic via Filebeat.</li> <li><strong>Metrics</strong>: Expose Prometheus metrics with <code>django‑prometheus</code> and scrape them via <code>node_exporter</code>.</li> <li><strong>Tracing</strong>: Integrate OpenTelemetry SDK and send traces to Jaeger.</li> </ul> <pre><code># Example of OpenTelemetry init in Django import opentelemetry.instrumentation.django from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.jaeger.thrift import JaegerExporter

trace.set_tracer_provider(TracerProvider()) jaeger_exporter = JaegerExporter( agent_host_name='jaeger', agent_port=6831, ) trace.get_tracer_provider().add_span_processor( BatchSpanProcessor(jaeger_exporter) ) opentelemetry.instrumentation.django.DjangoInstrumentor().instrument() </code></pre>

<h3>Automated Backups & Disaster Recovery</h3> <p>Schedule daily logical backups with <code>pg_dump</code> and store them in encrypted S3 buckets. Test point‑in‑time recovery quarterly.</p> <pre><code>0 2 * * * /usr/bin/pg_dump -U myuser mydb | gzip > /backups/mydb_$(date +\%F).sql.gz aws s3 cp /backups/ s3://my‑prod‑backups/ --recursive --storage-class GLACIER </code></pre> <p>By combining these measures, you achieve a resilient, secure, and scalable Django production environment.</p>

FAQs

<h2>Frequently Asked Questions</h2> <ol> <li><strong>Do I need Docker for a Django production deployment?</strong><br> Docker is optional. It simplifies dependency management and enables immutable infrastructure, but a traditional VM setup with system packages works equally well if you prefer manual control. </li> <li><strong>How many Gunicorn workers should I run?</strong><br> A good starting point is <code>(2 × CPU cores) + 1</code>. Adjust based on response times, memory consumption, and the concurrency model of your code (blocking vs async). </li> <li><strong>Can I serve static files directly from Django?</strong><br> Never in production. Django's <code>collectstatic</code> should gather assets into <code>STATIC_ROOT</code>, after which Nginx (or a CDN) serves them efficiently with proper caching headers. </li> <li><strong>What is the recommended way to manage environment variables?</strong><br> Use a <code>.env</code> file loaded by <code>python‑dotenv</code> for local development and a secrets manager (AWS Secrets Manager, Vault, etc.) for production. Never commit secrets to version control. </li> <li><strong>How do I perform zero‑downtime deployments?</strong><br> Leverage a blue‑green or rolling deployment strategy. With systemd, you can start a new Gunicorn instance on a different socket, switch Nginx upstreams, and gracefully stop the old workers after existing requests complete. </li> </ol>

Conclusion

<h2>Bringing It All Together</h2> <p>Deploying Django to production is a disciplined process that blends architecture design, server hardening, and operational excellence. By isolating concerns-using Nginx for TLS and static assets, Gunicorn for WSGI execution, PostgreSQL for reliable data storage, and Redis for caching and background jobs-you create a foundation that scales linearly as traffic grows.</p> <p>The code snippets above demonstrate concrete steps: creating a non‑root user, configuring a virtual environment, writing production‑ready settings, wiring systemd and Nginx, and securing the whole stack with HTTPS, CSP, and secret management. Finally, the monitoring, logging, and backup strategies ensure you can spot issues early and recover quickly.</p> <p>Implement these patterns, adapt them to your cloud provider, and your Django application will be ready to serve real users with confidence and performance.</p>