Python Django Deployment with Gunicorn and Nginx on a VPS in 2026
Django includes a development server (manage.py runserver) that works fine for local development. For production on a VPS, you need Gunicorn (the WSGI server) behind Nginx (reverse proxy handling SSL and static files).
Stack Overview
Internet → Nginx (SSL, static files) → Gunicorn (Django WSGI workers) → Django (Python)
Nginx handles the slow parts (SSL handshake, serving static files, connection keep-alive). Gunicorn handles fast Python request processing. Neither alone does both well.
Installation
# In your project directory
source venv/bin/activate
pip install gunicorn
# Test Gunicorn manually:
gunicorn --bind 0.0.0.0:8000 myproject.wsgi:application
Gunicorn Systemd Service
# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn Django Server
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/myproject
EnvironmentFile=/var/www/myproject/.env
ExecStart=/var/www/myproject/venv/bin/gunicorn \
--access-logfile /var/log/gunicorn/access.log \
--error-logfile /var/log/gunicorn/error.log \
--workers 3 \
--bind unix:/run/gunicorn.sock \
myproject.wsgi:application
[Install]
WantedBy=multi-user.target
Workers formula: 2 × CPU cores + 1. For a 2-core VPS: 5 workers.
Nginx Configuration
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri; # Force HTTPS
}
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# Serve Django static files directly (bypass Gunicorn)
location /static/ {
alias /var/www/myproject/staticfiles/;
}
location /media/ {
alias /var/www/myproject/media/;
}
# Proxy everything else 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;
}
}
Environment Variables
Never hardcode secrets. Use a .env file not tracked by git:
# /var/www/myproject/.env
SECRET_KEY=your_production_secret_key
DEBUG=False
ALLOWED_HOSTS=yourdomain.com
DATABASE_URL=postgresql://user:pass@localhost/dbname