Django Application Deployment on Linux

Django is a mature Python web framework providing batteries-included functionality for rapid application development with built-in admin interface, ORM, authentication, and security features. Deploying Django applications requires Python virtual environments, application servers like Gunicorn, Nginx reverse proxy, PostgreSQL database, static file serving, media file handling, and proper environment configuration. This guide covers production-ready Django deployment including virtual environment setup, Gunicorn configuration, Nginx proxy setup, PostgreSQL integration, static/media files, database migrations, and performance optimization for Linux servers.

Table of Contents

Django Architecture Overview

Django applications consist of projects containing multiple apps with models, views, URLs, and templates. Understanding the architecture helps with deployment configuration.

Request flow in Django:

  1. Nginx receives HTTP request
  2. Nginx forwards to Gunicorn via Unix socket
  3. Gunicorn worker process spawned
  4. Django receives request
  5. URL routing resolves to view
  6. View executes business logic
  7. Template rendered or JSON returned
  8. Response sent back to Nginx
  9. Nginx caches or returns to client

Key deployment considerations:

  • Django runs as dedicated user (not root)
  • Virtual environment isolates dependencies
  • Static files collected and served by Nginx
  • Media files stored outside web root
  • Database migrations set up schema
  • Secret key and debug mode configured
  • Allowed hosts restricts access
  • Logging configured for monitoring

Server Preparation

Prepare Linux server for Django deployment.

Update system:

sudo apt update
sudo apt upgrade -y
sudo apt install curl wget git zip unzip vim htop build-essential -y

Install Python 3.11:

sudo apt install python3.11 python3.11-venv python3.11-dev -y
sudo apt install python3-pip -y

# Verify
python3 --version
pip3 --version

Install PostgreSQL:

sudo apt install postgresql postgresql-contrib -y
sudo systemctl start postgresql
sudo systemctl enable postgresql

Install Nginx:

sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx

Create application user:

sudo useradd -m -s /bin/bash django
sudo usermod -aG www-data django

# Create application directory
sudo mkdir -p /home/django/app
sudo chown -R django:www-data /home/django/app

Install build dependencies:

# Required for compiling Python packages
sudo apt install libpq-dev libssl-dev libffi-dev -y

Python Virtual Environment

Isolate application dependencies using virtual environment.

Create virtual environment:

sudo -u django python3.11 -m venv /home/django/venv

# Activate (for testing/installation)
source /home/django/venv/bin/activate

# Verify
python --version

Install pip requirements:

# Upgrade pip in virtual environment
/home/django/venv/bin/pip install --upgrade pip setuptools wheel

# Install common Django packages
/home/django/venv/bin/pip install django gunicorn psycopg2-binary python-dotenv whitenoise

Create requirements.txt:

# Generate from existing installation
/home/django/venv/bin/pip freeze > /home/django/app/requirements.txt

# Or create manually
cat > /home/django/app/requirements.txt << 'EOF'
Django==4.2.0
gunicorn==21.2.0
psycopg2-binary==2.9.6
python-dotenv==1.0.0
whitenoise==6.4.0
celery==5.2.7
redis==4.5.5
EOF

Django Application Setup

Install and configure Django application.

Clone or create Django project:

cd /home/django/app

# Clone from repository
sudo -u django git clone https://github.com/yourname/django-app.git .

# Or create new project
/home/django/venv/bin/django-admin startproject config .

Install project dependencies:

/home/django/venv/bin/pip install -r requirements.txt

Create .env file:

sudo -u django cat > /home/django/app/.env << 'EOF'
DEBUG=False
SECRET_KEY=your-secret-key-here
ALLOWED_HOSTS=example.com,www.example.com
DATABASE_URL=postgresql://django_user:password@localhost/django_db
REDIS_URL=redis://127.0.0.1:6379/0
EOF

Generate secret key:

python3 << 'EOF'
import secrets
print(secrets.token_urlsafe(50))
EOF

# Update .env with generated key

Update Django settings:

sudo -u django nano /home/django/app/config/settings.py

Configure for production:

# security settings
DEBUG = False
ALLOWED_HOSTS = ['example.com', 'www.example.com']
SECRET_KEY = 'from .env file'

# Database
import os
from urllib.parse import urlparse

db_url = os.getenv('DATABASE_URL')
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'django_db',
        'USER': 'django_user',
        'PASSWORD': 'SecurePassword123!',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}

# Cache configuration
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

# Static files
STATIC_URL = '/static/'
STATIC_ROOT = '/home/django/app/staticfiles/'

# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = '/home/django/app/media/'

# Security
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_SECURITY_POLICY = {
    'default-src': ("'self'",),
}

# Logging
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'file': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': '/home/django/app/logs/django.log',
        },
    },
    'root': {
        'handlers': ['file'],
        'level': 'INFO',
    },
}

Create logs directory:

sudo -u django mkdir -p /home/django/app/logs
sudo -u django touch /home/django/app/logs/django.log

PostgreSQL Database

Set up PostgreSQL database and user.

Connect to PostgreSQL:

sudo -u postgres psql

Create database and user:

CREATE DATABASE django_db;
CREATE USER django_user WITH PASSWORD 'SecurePassword123!';
ALTER ROLE django_user SET client_encoding TO 'utf8';
ALTER ROLE django_user SET default_transaction_isolation TO 'read committed';
ALTER ROLE django_user SET default_transaction_deferrable TO on;
ALTER ROLE django_user SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE django_db TO django_user;
\q

Run migrations:

cd /home/django/app
/home/django/venv/bin/python manage.py migrate

# Output shows: running migrations, applied migrations

Create superuser:

/home/django/venv/bin/python manage.py createsuperuser

# Enter username, email, password when prompted

Collect static files:

/home/django/venv/bin/python manage.py collectstatic --noinput

# Copies all static files to STATIC_ROOT directory

Gunicorn Application Server

Configure Gunicorn to run Django application.

Create Gunicorn configuration:

sudo -u django cat > /home/django/app/gunicorn_config.py << 'EOF'
import multiprocessing

# Server socket
bind = 'unix:/tmp/gunicorn.sock'
backlog = 2048

# Worker processes
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'sync'
worker_connections = 1000
timeout = 30
keepalive = 5

# Logging
accesslog = '/home/django/app/logs/gunicorn_access.log'
errorlog = '/home/django/app/logs/gunicorn_error.log'
loglevel = 'info'

# Server mechanics
daemon = False
umask = 0
user = 'django'
group = 'www-data'
tmp_upload_dir = None

# Server hooks
def post_fork(server, worker):
    server.log.info("Worker spawned (pid: %s)", worker.pid)

def pre_fork(server, worker):
    pass

def pre_exec(server):
    server.log.info("Forking new master process")

def when_ready(server):
    server.log.info("Gunicorn server is ready. Spawning workers")

def on_exit(server):
    server.log.info("Gunicorn exiting")
EOF

Create systemd service:

sudo cat > /etc/systemd/system/gunicorn.service << 'EOF'
[Unit]
Description=Gunicorn application server
After=network.target

[Service]
Type=notify
User=django
Group=www-data
WorkingDirectory=/home/django/app
ExecStart=/home/django/venv/bin/gunicorn --config gunicorn_config.py config.wsgi
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable gunicorn
sudo systemctl start gunicorn
sudo systemctl status gunicorn

Verify Gunicorn:

# Check if socket created
ls -la /tmp/gunicorn.sock

# Check logs
tail -f /home/django/app/logs/gunicorn_error.log

Nginx Reverse Proxy

Configure Nginx to proxy requests to Gunicorn.

Create Nginx configuration:

sudo nano /etc/nginx/sites-available/django.conf

Apply configuration:

upstream gunicorn {
    server unix:/tmp/gunicorn.sock fail_timeout=0;
}

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;
    client_max_body_size 20M;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    access_log /var/log/nginx/django_access.log;
    error_log /var/log/nginx/django_error.log;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    # Static files
    location /static/ {
        alias /home/django/app/staticfiles/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Media files
    location /media/ {
        alias /home/django/app/media/;
        expires 7d;
    }

    # Proxy to Gunicorn
    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;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

Enable configuration:

sudo ln -s /etc/nginx/sites-available/django.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Static and Media Files

Properly handle Django static and media files.

Collect static files:

cd /home/django/app
/home/django/venv/bin/python manage.py collectstatic --noinput

# Verify
ls -la /home/django/app/staticfiles/

Configure static file serving:

# Create symlink for web server access
sudo mkdir -p /var/www/django-static
sudo chown django:www-data /var/www/django-static
sudo ln -s /home/django/app/staticfiles/* /var/www/django-static/

Create media directory:

sudo -u django mkdir -p /home/django/app/media
sudo chmod 755 /home/django/app/media

# Set permissions for uploads
sudo chmod 775 /home/django/app/media

Configure file uploads:

# In Django settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

# Maximum upload size
FILE_UPLOAD_MAX_MEMORY_SIZE = 20971520  # 20 MB
DATA_UPLOAD_MAX_MEMORY_SIZE = 20971520  # 20 MB

Security Configuration

Implement security best practices.

Set secure environment:

# Protect .env file
sudo chmod 600 /home/django/app/.env

# Verify permissions
ls -la /home/django/app/.env

Configure HTTPS enforcement:

# In settings.py
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True

Set allowed hosts:

ALLOWED_HOSTS = ['example.com', 'www.example.com']

Configure security headers via Nginx:

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Performance Optimization

Optimize Django application for production.

Enable database connection pooling:

sudo apt install pgbouncer -y

# Configure pgbouncer for connection pooling
sudo nano /etc/pgbouncer/pgbouncer.ini

Configure Celery for async tasks:

# Install Celery
/home/django/venv/bin/pip install celery redis

# Create Celery configuration in Django
cat > /home/django/app/config/celery.py << 'EOF'
import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')

app = Celery('config')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
EOF

Create Celery worker service:

sudo cat > /etc/systemd/system/celery.service << 'EOF'
[Unit]
Description=Celery Service
After=network.target

[Service]
Type=forking
User=django
Group=www-data
WorkingDirectory=/home/django/app
ExecStart=/home/django/venv/bin/celery -A config worker --loglevel=info
Restart=always

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable celery
sudo systemctl start celery

Monitor performance:

# Check Gunicorn workers
ps aux | grep gunicorn

# Monitor database
psql -U django_user -d django_db -c "SELECT count(*) FROM pg_stat_activity;"

# Check logs
tail -f /home/django/app/logs/gunicorn_error.log
tail -f /home/django/app/logs/django.log

Conclusion

Deploying Django applications on Linux requires coordinated setup of Python virtual environments, Gunicorn application servers, Nginx reverse proxies, and PostgreSQL databases. This guide covers production-ready deployment with proper isolation, security hardening, static file handling, and performance optimization. Key focus areas are virtual environment setup ensuring dependency isolation, Gunicorn configuration for worker process management, Nginx reverse proxy for efficient request handling, PostgreSQL for reliable data persistence, and proper file permissions ensuring security. Regular monitoring of logs and application metrics ensures continued operation. Following these practices creates a robust Django deployment ready for production traffic.