Skip to main content
Back to Category

Python/Django Deploy: Production with Gunicorn and Nginx

Guide to deploying Python/Django applications on a VPS. Virtualenv setup, Django production settings, Gunicorn WSGI server, Nginx reverse proxy, systemd service, and static file management.

Read time: 18 min Web Hosting / Applications
pythondjangogunicornnginxdeployvpsvirtualenvwsgipostgresql

Table of Contents

Python/Django Deploy: Production Environment with Gunicorn and Nginx

Django is Python's most popular web framework. The industry-standard combination for production deployment is Gunicorn (WSGI server) and Nginx (reverse proxy). This guide walks you through deploying a Django application to a VPS from scratch.

Python and Virtualenv Setup

hljs bash
# System update
sudo apt update && sudo apt upgrade -y

# Install Python 3 and pip
sudo apt install python3 python3-pip python3-venv python3-dev -y

# Check Python version
python3 --version

# Create application directory
sudo mkdir -p /var/www/mydjango
sudo chown $USER:$USER /var/www/mydjango
cd /var/www/mydjango

# Create virtual environment
python3 -m venv venv

# Activate virtual environment
source venv/bin/activate

# Upgrade pip
pip install --upgrade pip

Use a separate virtual environment for each Django project. This prevents dependency conflicts and keeps projects isolated.

Transferring the Application to the Server

hljs bash
# Clone the repo
cd /var/www
git clone https://github.com/user/mydjango.git
cd mydjango

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

# Install dependencies
pip install -r requirements.txt

# Install Gunicorn
pip install gunicorn

Django Production Settings

hljs bash
# Create .env file
nano /var/www/mydjango/.env
hljs env
DJANGO_SECRET_KEY=very-secret-and-long-key-here
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=example.com,www.example.com
DATABASE_URL=postgresql://dbuser:dbpass@localhost:5432/mydb
REDIS_URL=redis://localhost:6379/0
hljs python
# In settings/production.py or settings.py
import os

# Security
DEBUG = False
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
ALLOWED_HOSTS = os.environ.get('DJANGO_ALLOWED_HOSTS', '').split(',')

# Security headers
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# Static files
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

Database Setup (PostgreSQL)

hljs bash
# Install PostgreSQL
sudo apt install postgresql postgresql-contrib -y

# Connect to PostgreSQL
sudo -u postgres psql
hljs sql
CREATE DATABASE mydb;
CREATE USER dbuser WITH PASSWORD 'strong-password';
ALTER ROLE dbuser SET client_encoding TO 'utf8';
ALTER ROLE dbuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE dbuser SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE mydb TO dbuser;
\q
hljs bash
# Install psycopg2
pip install psycopg2-binary

# Run migrations
python manage.py migrate

# Create superuser
python manage.py createsuperuser

# Collect static files
python manage.py collectstatic --noinput

Gunicorn Configuration

hljs python
# gunicorn.conf.py
bind = 'unix:/run/gunicorn/gunicorn.sock'
workers = 3  # (2 * CPU cores) + 1
worker_class = 'sync'
timeout = 30
max_requests = 1000
max_requests_jitter = 50
preload_app = True
user = 'www-data'
group = 'www-data'
errorlog = '/var/log/gunicorn/error.log'
accesslog = '/var/log/gunicorn/access.log'
loglevel = 'info'

Systemd Service File

hljs bash
sudo mkdir -p /var/log/gunicorn /run/gunicorn
sudo chown www-data:www-data /var/log/gunicorn /run/gunicorn
sudo nano /etc/systemd/system/gunicorn.service
hljs ini
[Unit]
Description=Gunicorn daemon for Django
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/mydjango
ExecStart=/var/www/mydjango/venv/bin/gunicorn \
    --config /var/www/mydjango/gunicorn.conf.py \
    myproject.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
EnvironmentFile=/var/www/mydjango/.env

[Install]
WantedBy=multi-user.target
hljs bash
sudo systemctl daemon-reload
sudo systemctl start gunicorn
sudo systemctl enable gunicorn
sudo systemctl status gunicorn

Nginx Reverse Proxy Configuration

hljs nginx
server {
    listen 80;
    server_name example.com www.example.com;

    # Static files
    location /static/ {
        alias /var/www/mydjango/staticfiles/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Media files
    location /media/ {
        alias /var/www/mydjango/media/;
    }

    # Proxy to Gunicorn
    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn/gunicorn.sock;
        proxy_read_timeout 300;
        proxy_connect_timeout 300;
    }

    client_max_body_size 100M;
}
hljs bash
sudo ln -s /etc/nginx/sites-available/mydjango /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

SSL Certificate

hljs bash
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com

Deployment Workflow

hljs bash
cd /var/www/mydjango
source venv/bin/activate
git pull origin main
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --noinput
sudo systemctl restart gunicorn

On REXE servers, at least 1 GB RAM is recommended for Django. Adjust the Gunicorn worker count based on CPU cores: (2 × CPU) + 1.

Conclusion

Your Django application is now running in production with Gunicorn WSGI server and Nginx reverse proxy. Thanks to the systemd service, the application starts automatically when the server reboots.