Back to Blog

Deploying Django to Production: Complete Server Setup Guide

admin
November 21, 2025 4 min read
194 views
Step-by-step guide to deploying Django with Gunicorn, Nginx, PostgreSQL, and SSL on a Linux server.

Deploying Django to Production

Taking your Django app from development to production requires careful setup. Here's our complete deployment guide.

Server Requirements

  • Ubuntu 22.04 LTS (or similar)
  • 2+ GB RAM
  • Python 3.11+
  • PostgreSQL 15+
  • Nginx
  • SSL certificate (Let's Encrypt)

Initial Server Setup

# Update system
sudo apt update && sudo apt upgrade -y

# Install dependencies
sudo apt install -y python3-pip python3-venv python3-dev \
    postgresql postgresql-contrib \
    nginx certbot python3-certbot-nginx \
    supervisor git curl

# Create app user
sudo useradd -m -s /bin/bash djzen
sudo usermod -aG sudo djzen

PostgreSQL Setup

# Create database and user
sudo -u postgres psql

CREATE DATABASE djzen;
CREATE USER djzen_user WITH PASSWORD 'secure_password_here';
ALTER ROLE djzen_user SET client_encoding TO 'utf8';
ALTER ROLE djzen_user SET default_transaction_isolation TO 'read committed';
ALTER ROLE djzen_user SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE djzen TO djzen_user;
\q

Application Setup

# Switch to app user
sudo su - djzen

# Clone repository
git clone https://github.com/yourusername/djzen.git
cd djzen

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

# Install dependencies
pip install -r requirements.txt
pip install gunicorn psycopg2-binary

# Create environment file
cat > .env << EOF
DEBUG=False
SECRET_KEY=$(python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())')
DATABASE_URL=postgres://djzen_user:secure_password_here@localhost:5432/djzen
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
STATIC_ROOT=/var/www/djzen/static
MEDIA_ROOT=/var/www/djzen/media
EOF

# Run migrations
python manage.py migrate

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

# Create superuser
python manage.py createsuperuser

Gunicorn Configuration

# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn daemon for DjangoZen
After=network.target

[Service]
User=djzen
Group=www-data
WorkingDirectory=/home/djzen/djzen
ExecStart=/home/djzen/djzen/venv/bin/gunicorn \
    --access-logfile /var/log/gunicorn/access.log \
    --error-logfile /var/log/gunicorn/error.log \
    --workers 3 \
    --bind unix:/run/gunicorn/gunicorn.sock \
    --timeout 120 \
    djzen.wsgi:application

[Install]
WantedBy=multi-user.target
# Create directories
sudo mkdir -p /run/gunicorn /var/log/gunicorn
sudo chown djzen:www-data /run/gunicorn /var/log/gunicorn

# Enable and start
sudo systemctl enable gunicorn
sudo systemctl start gunicorn

Nginx Configuration

# /etc/nginx/sites-available/djzen
upstream gunicorn {
    server unix:/run/gunicorn/gunicorn.sock fail_timeout=0;
}

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    # Redirect HTTP to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

    # SSL certificates (managed by Certbot)
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    # SSL settings
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

    # Security headers
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;

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

    # Media files
    location /media/ {
        alias /var/www/djzen/media/;
        expires 7d;
    }

    # Application
    location / {
        proxy_pass http://gunicorn;
        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;
        proxy_redirect off;

        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # Error pages
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /var/www/html;
    }
}
# Enable site
sudo ln -s /etc/nginx/sites-available/djzen /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

SSL Certificate

# Get certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Auto-renewal (already configured by Certbot)
sudo certbot renew --dry-run

Redis & Celery Setup

# Install Redis
sudo apt install redis-server
sudo systemctl enable redis-server

# Celery service
# /etc/systemd/system/celery.service
[Unit]
Description=Celery Worker
After=network.target

[Service]
User=djzen
Group=www-data
WorkingDirectory=/home/djzen/djzen
ExecStart=/home/djzen/djzen/venv/bin/celery -A djzen worker -l INFO

[Install]
WantedBy=multi-user.target

# Celery Beat
# /etc/systemd/system/celerybeat.service
[Unit]
Description=Celery Beat Scheduler
After=network.target

[Service]
User=djzen
Group=www-data
WorkingDirectory=/home/djzen/djzen
ExecStart=/home/djzen/djzen/venv/bin/celery -A djzen beat -l INFO

[Install]
WantedBy=multi-user.target

Deployment Script

#!/bin/bash
# deploy.sh

set -e

echo "Deploying DjangoZen..."

cd /home/djzen/djzen
source venv/bin/activate

# Pull latest code
git pull origin main

# Install dependencies
pip install -r requirements.txt

# Run migrations
python manage.py migrate --noinput

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

# Restart services
sudo systemctl restart gunicorn
sudo systemctl restart celery
sudo systemctl restart celerybeat

echo "Deployment complete!"

Monitoring

# Check service status
sudo systemctl status gunicorn
sudo systemctl status nginx
sudo systemctl status celery

# View logs
sudo journalctl -u gunicorn -f
sudo tail -f /var/log/nginx/error.log
sudo tail -f /var/log/gunicorn/error.log

Security Checklist

  • [ ] DEBUG = False
  • [ ] Strong SECRET_KEY
  • [ ] HTTPS enforced
  • [ ] Database password secure
  • [ ] Firewall configured (ufw)
  • [ ] Regular backups
  • [ ] Monitoring alerts
  • [ ] Security updates enabled

Your Django app is now production-ready!

Comments (0)

Please login to leave a comment.

No comments yet. Be the first to comment!