
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Most Django tutorials stop at runserver, leaving teams unprepared when traffic hits or audits begin. To successfully deploy Django to production, you must replace the development server with an application server like Gunicorn behind a reverse proxy such as Nginx, managed by systemd for resilience. This guide walks through the exact configuration I use for client projects, ensuring your stack is secure, observable, and performant from day one.
How do you configure Gunicorn to deploy Django to production reliably?
Gunicorn is the de facto standard WSGI server for Django in 2026. It handles concurrency, worker recycling, and graceful restarts—none of which manage.py runserver supports. The key is binding to a Unix socket rather than a TCP port when sitting behind Nginx on the same host; this avoids TCP overhead and restricts access to the local filesystem.
Create a dedicated Gunicorn config file
Avoid long CLI flags in systemd units. Instead, create /etc/gunicorn/myproject.py:
<!-- /etc/gunicorn/myproject.py -->
bind = "unix:/run/gunicorn/myproject.sock"
workers = 4 # Start with (2 × CPU cores) + 1
worker_class = "gthread"
threads = 2
max_requests = 1000
max_requests_jitter = 50
timeout = 120
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
loglevel = "info" - Workers: For CPU-bound Django views, use sync workers. For I/O-heavy workloads (database queries, API calls),
gthreadorgeventprevents blocking. Test both under load. - Max requests: Recycle workers after 1,000 requests to prevent memory leaks—a common issue in long-running Django processes.
- Socket permissions: Ensure the
/run/gunicorn/directory is owned by your app user and thewww-datagroup so Nginx can read the socket.
If you’re new to Linux service management, review systemd services and timers before proceeding. Proper unit files are non-negotiable for production reliability.
What Nginx settings are required when you deploy Django to production?
Nginx serves three critical roles: terminating TLS, serving static/media files directly (bypassing Python entirely), and buffering slow clients so Gunicorn workers aren’t held hostage. Misconfigurations here cause 502 errors, slow page loads, and security gaps.
Optimized Nginx server block
<!-- /etc/nginx/sites-available/myproject -->
upstream django_app {
server unix:/run/gunicorn/myproject.sock fail_timeout=0;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Security headers
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
location /static/ {
alias /var/www/myproject/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location /media/ {
alias /var/www/myproject/media/;
expires 30d;
}
location / {
proxy_pass http://django_app;
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;
proxy_redirect off;
# Buffering protects Gunicorn from slow clients
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 8 8k;
}
} Critical details often missed:
fail_timeout=0in upstream prevents Nginx from marking the socket as failed during deployments or brief restarts.X-Forwarded-Protoensures Django generates HTTPS URLs in redirects and emails. Without it, you’ll get mixed-content warnings and broken password reset links.- Static file collection: Run
python manage.py collectstatic --noinputduring deployment, not at runtime. Automate this in your CI pipeline or Ansible playbook.
For TLS automation, see setting up free SSL with Let’s Encrypt. Manual certificate management doesn’t scale and causes outages.
How do you manage Django with systemd for zero-downtime deploys?
Systemd provides automatic restarts, logging integration, resource limits, and dependency ordering. A well-written unit file is what separates hobbyist setups from production-grade deployments.
Robust systemd service unit
<!-- /etc/systemd/system/myproject.service -->
[Unit]
Description=Django Application (Gunicorn)
After=network.target postgresql.service redis.service
Requires=postgresql.service
[Service]
User=myproject
Group=www-data
RuntimeDirectory=/run/gunicorn
WorkingDirectory=/var/www/myproject
EnvironmentFile=/etc/myproject/env
ExecStart=/var/www/myproject/venv/bin/gunicorn -c /etc/gunicorn/myproject.py myproject.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=5
KillMode=mixed
TimeoutStopSec=30
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/var/www/myproject/media /var/log/gunicorn /run/gunicorn
[Install]
WantedBy=multi-user.target Key operational practices:
EnvironmentFile: Store secrets (DATABASE_URL,SECRET_KEY) here withchmod 600ownership by root. Never put secrets in the unit file or git.ExecReload: SendHUPfor graceful reloads during deploys. New code loads without dropping active connections.ReadWritePaths: Restrict write access to only necessary directories. This contains damage if your app is compromised.- Socket activation (optional): For high-traffic sites, pair with a
.socketunit so systemd creates the socket before starting Gunicorn, eliminating race conditions on boot.
Always validate with systemd-analyze verify myproject.service before enabling. Syntax errors silently fail in production.
What security hardening steps are mandatory when you deploy Django to production?
Django’s defaults are safe for development but insufficient for production. In 2026, compliance frameworks (SOC 2, ISO 27001) and automated scanners expect these controls. Skipping them invites breaches and failed audits.
| Control | Django Setting / Action | Why It Matters |
|---|---|---|
| Debug mode | DEBUG = False | Prevents stack trace exposure and sensitive data leakage |
| Allowed hosts | ALLOWED_HOSTS = ['example.com'] | Blocks HTTP Host header attacks and cache poisoning |
| Secure cookies | SESSION_COOKIE_SECURE = TrueCSRF_COOKIE_SECURE = True | Ensures cookies transmit only over HTTPS |
| HSTS | SECURE_HSTS_SECONDS = 63072000 | Forces browsers to use HTTPS, preventing downgrade attacks |
| Content security policy | django-csp middleware | Mitigates XSS by restricting script sources |
| Secret key rotation | Use environment variables + vault | Limits blast radius if secrets leak; enables rotation without downtime |
Beyond Django settings, harden the OS layer:
- Run Gunicorn as a non-root user with minimal privileges.
- Enable UFW or nftables to allow only ports 80/443. See configuring a firewall with UFW.
- Automate patching with unattended-upgrades for security updates.
- Scan dependencies regularly with
safety checkor Snyk in CI.
For teams handling Nepali user data or fintech workloads, also review data protection basics for Nepal fintech to align with local regulatory expectations around encryption and access logging.
Deploy Django to Production With Confidence
The gap between a working demo and a production-ready Django application isn’t complexity—it’s discipline. Every configuration above has been battle-tested across financial services, e-commerce, and SaaS platforms serving users globally and within Nepal. Start with the Gunicorn-Nginx-systemd trio, enforce security headers from day one, and treat your deployment artifacts as code. If your team needs help auditing an existing setup or building a compliant pipeline from scratch, reach out to discuss your infrastructure. Reliable deployments aren’t accidental; they’re engineered.