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.
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
# 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
# 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
# Create .env file
nano /var/www/mydjango/.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
# 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)
# Install PostgreSQL
sudo apt install postgresql postgresql-contrib -y
# Connect to PostgreSQL
sudo -u postgres psql
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
# 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
# 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
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
[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
sudo systemctl daemon-reload
sudo systemctl start gunicorn
sudo systemctl enable gunicorn
sudo systemctl status gunicorn
Nginx Reverse Proxy Configuration
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;
}
sudo ln -s /etc/nginx/sites-available/mydjango /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
SSL Certificate
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com
Deployment Workflow
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.
Related Articles
Deploy Node.js App: PM2 and Nginx Guide
Complete guide to deploying Node.js applications on a VPS. Node.js installation with nvm, PM2 process manager, Nginx reverse proxy, SSL certificate, and environment variables management.
Deploy PHP App: LAMP and LEMP Stack Guide
Complete guide to deploying PHP applications on a VPS. LAMP vs LEMP comparison, Apache/Nginx + PHP-FPM + MySQL setup, virtual host configuration, and PHP version management.
WordPress Installation and Optimization: VPS Guide
Guide to installing and optimizing WordPress on a VPS. LEMP stack setup, wp-config.php settings, file permissions, SSL, OPcache performance optimization, and security hardening.