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
Author
REXE Teknoloji Network & Security Team
Editor
REXE Teknoloji Technical Editorial
First published
Last updated

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.

Frequently Asked Questions

Gunicorn won't start, how do I debug it?

Check the service status with 'sudo systemctl status gunicorn'. Review recent logs with 'sudo journalctl -u gunicorn -n 50'. Common causes: wrong WSGI module path, missing dependencies, or incorrect values in the .env file. Verify the virtual environment is at the correct path.

Django static files aren't loading (missing CSS/JS).

Make sure you've run 'python manage.py collectstatic'. Check that the /static/ location block in Nginx correctly aliases to the STATIC_ROOT directory. Verify STATIC_ROOT is correctly set in settings.py. Check that Nginx (as www-data) can access the staticfiles directory.

I'm getting a 500 Internal Server Error in Django.

Set DEBUG=True in settings.py to see error details (development only). Review Gunicorn logs with 'sudo journalctl -u gunicorn'. Check the Django log file (/var/log/django/error.log). Verify database connection and migration status.

How do I run multiple Django applications on the same server?

Create a separate virtual environment, separate Gunicorn socket file, and separate systemd service file for each application. In Nginx, add a separate server block for each domain with proxy configuration pointing to the relevant Gunicorn socket. Make sure each application uses a different database.

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.

15 min
nodejspm2nginx

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.

18 min
phplamplemp
Network TrafficInbound GbpsOutbound Gbps