DevOps Intermediate

Deploy Django with Gunicorn & Nginx on Linux

Step-by-step guide to deploying a Django application on a Linux server with Gunicorn, Nginx, and systemd. Production-ready setup.

DjangoZen Team Mar 29, 2026 4 min read 28 views

Deploying Django means moving from the development server (manage.py runserver) to a production-grade setup. This guide walks you through deploying Django on a Linux VPS using Gunicorn as the application server and Nginx as the reverse proxy.

Architecture Overview

In production, requests flow through this stack:

Client (Browser)Nginx (reverse proxy, static files, SSL)Gunicorn (WSGI application server)Django (your application)PostgreSQL (database)
Why not just use runserver? Django's development server is single-threaded, has no security hardening, doesn't serve static files efficiently, and will crash under real traffic. Never use it in production.

Step 1: Server Setup

# Update system
sudo apt update && sudo apt upgrade -y

# Install dependencies
sudo apt install python3 python3-pip python3-venv nginx postgresql git

# Create a system user for the app
sudo useradd --system --shell /bin/bash --home /opt/myproject myproject
sudo mkdir -p /opt/myproject
sudo chown myproject:myproject /opt/myproject

Step 2: Deploy Your Code

# Clone your project
cd /opt/myproject
git clone https://github.com/you/myproject.git app
cd app

# Create virtual environment
python3 -m venv venv
source venv/bin/activate

# Install dependencies
pip install -r requirements.txt
pip install gunicorn

Step 3: Configure Django for Production

# settings.py (or use a separate production settings file)
import os

DEBUG = False

ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']

# Static files
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

# Security
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
# Collect static files
python manage.py collectstatic --noinput

# Run migrations
python manage.py migrate
Critical: Set DEBUG = False in production. With DEBUG = True, Django exposes detailed error pages with source code, settings, and database queries to anyone.

Step 4: Set Up Gunicorn

# Test Gunicorn manually first
gunicorn myproject.wsgi:application --bind 0.0.0.0:8000

# Create systemd service file
sudo nano /etc/systemd/system/gunicorn.service
# /etc/systemd/system/gunicorn.service
[Unit]
Description=gunicorn daemon for myproject
After=network.target

[Service]
User=myproject
Group=www-data
WorkingDirectory=/opt/myproject/app
ExecStart=/opt/myproject/app/venv/bin/gunicorn \
    --workers 3 \
    --bind unix:/run/gunicorn.sock \
    myproject.wsgi:application
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
# Enable and start
sudo systemctl daemon-reload
sudo systemctl start gunicorn
sudo systemctl enable gunicorn

# Check status
sudo systemctl status gunicorn
Workers formula: Set workers to 2 * CPU_cores + 1. For a 2-core VPS, use 5 workers. For a 4-core server, use 9.

Step 5: Configure Nginx

# /etc/nginx/sites-available/myproject
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    # Static files
    location /static/ {
        alias /opt/myproject/app/staticfiles/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Media files
    location /media/ {
        alias /opt/myproject/app/media/;
        expires 30d;
    }

    # Pass all other requests to Gunicorn
    location / {
        proxy_pass http://unix:/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;
    }
}
# Enable the site
sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/

# Test config
sudo nginx -t

# Restart Nginx
sudo systemctl restart nginx

Step 6: SSL with Let's Encrypt

# Install Certbot
sudo apt install certbot python3-certbot-nginx

# Get SSL certificate (auto-configures Nginx)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Auto-renewal is set up automatically
# Test it with:
sudo certbot renew --dry-run

Deployment Checklist

# Run Django's deployment checks
python manage.py check --deploy
CheckStatus
DEBUG = FalseRequired
SECRET_KEY from environment variableRequired
ALLOWED_HOSTS setRequired
HTTPS / SSL certificateRequired
Database backups configuredRequired
Static files collectedRequired
Firewall (UFW) configuredRecommended
Log rotation set upRecommended
Monitoring (uptime checks)Recommended

Common Commands

# Restart after code changes
sudo systemctl restart gunicorn

# View logs
sudo journalctl -u gunicorn --no-pager -n 50

# Nginx error logs
sudo tail -f /var/log/nginx/error.log

Summary

  • Never use runserver in production
  • Gunicorn handles Python/Django, Nginx handles HTTP/static/SSL
  • Use systemd to manage Gunicorn as a service
  • Always enable HTTPS with Let's Encrypt (free)
  • Run manage.py check --deploy before going live