Step-by-step guide to deploying a Django application on a Linux server with Gunicorn, Nginx, and systemd. Production-ready setup.
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.
In production, requests flow through this stack:
Client (Browser)
↓
Nginx (reverse proxy, static files, SSL)
↓
Gunicorn (WSGI application server)
↓
Django (your application)
↓
PostgreSQL (database)
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.
# 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
# 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
# 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
DEBUG = False in production. With DEBUG = True, Django exposes detailed error pages with source code, settings, and database queries to anyone.
# 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
2 * CPU_cores + 1. For a 2-core VPS, use 5 workers. For a 4-core server, use 9.
# /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
# 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
# Run Django's deployment checks
python manage.py check --deploy
| Check | Status |
|---|---|
| DEBUG = False | Required |
| SECRET_KEY from environment variable | Required |
| ALLOWED_HOSTS set | Required |
| HTTPS / SSL certificate | Required |
| Database backups configured | Required |
| Static files collected | Required |
| Firewall (UFW) configured | Recommended |
| Log rotation set up | Recommended |
| Monitoring (uptime checks) | Recommended |
# 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
runserver in productionmanage.py check --deploy before going live