Tutorial22 min read

How to Deploy a Django App to Production in 2025

Complete production-ready Django deployment guide. From zero to production with Gunicorn, Nginx, PostgreSQL, SSL, monitoring, and auto-scaling.

What You'll Learn:

  • ✓ Server setup on Vultr/DigitalOcean (with $500 free credits)
  • ✓ Deploy your Django app with Gunicorn & systemd
  • ✓ Configure Nginx as reverse proxy
  • ✓ Set up PostgreSQL database
  • ✓ Configure static files & media storage
  • ✓ Set up free SSL with Let's Encrypt
  • ✓ Implement monitoring and logging
  • ✓ Set up CI/CD with GitHub Actions
  • ✓ Scale horizontally with load balancing

Prerequisites

Before we begin, make sure you have:

  • A Django app ready to deploy (Django 4.2+ or 5.0+)
  • Your code in a Git repository (GitHub, GitLab, etc.)
  • A domain name (optional but recommended)
  • Basic command line knowledge
  • Familiarity with Django settings and migrations

Step 1: Choose Your Hosting Provider

You have three main options for Django hosting. I'll show you how to deploy on all three, but I recommend starting with Vultr or DigitalOcean for the best balance of simplicity, performance, and cost.

Vultr (Recommended)

Best value, fastest deployment

  • ✓ $300 free credits
  • ✓ Cheapest pricing ($6/mo starts)
  • ✓ 32 global locations
  • ✓ Deploy in 60 seconds
  • ✓ Excellent for Python/Django

DigitalOcean

Best docs, great for beginners

  • ✓ $200 free credits
  • ✓ Best Django documentation
  • ✓ Simple UI
  • ✓ Great community tutorials

Step 2: Create Your Server

For Vultr Users:

  1. Click the button above to get your $300 credits
  2. Click "Deploy +""Deploy New Server"
  3. Choose Ubuntu 24.04 LTS
  4. Select plan: $6/mo (1 vCPU, 1GB RAM) for small apps, $12/mo (1 vCPU, 2GB RAM) for production
  5. Choose location closest to your users
  6. Add SSH key or set root password
  7. Click "Deploy Now"

For DigitalOcean Users:

  1. Click the button above to get your $200 credits
  2. Click "Create""Droplets"
  3. Choose Ubuntu 24.04 LTS
  4. Select plan: Basic $6/mo (1 vCPU, 1GB RAM) for small apps, $12/mo (2GB RAM) for production
  5. Choose datacenter region
  6. Add SSH key or set root password
  7. Click "Create Droplet"

💡 Pro Tip:

For production Django apps, I recommend at least 2GB RAM ($12/mo) to handle PostgreSQL, Nginx, and Gunicorn comfortably. You can start with 1GB and upgrade later if needed.

Step 3: Initial Server Setup

SSH into your server:

ssh root@your_server_ip

Update System Packages

apt update && apt upgrade -y

Install Required Packages

# Install Python, pip, and essential tools
apt install python3 python3-pip python3-venv python3-dev -y

# Install PostgreSQL
apt install postgresql postgresql-contrib -y

# Install Nginx
apt install nginx -y

# Install system dependencies for common Python packages
apt install build-essential libpq-dev libssl-dev libffi-dev -y

# Install Git
apt install git -y

Create a Deployment User (Security Best Practice)

# Create user
adduser django --disabled-password --gecos ""

# Add to sudo group
usermod -aG sudo django

# Switch to django user
su - django

Step 4: Set Up PostgreSQL Database

Switch to postgres user and create database:

# Switch to postgres user
sudo -u postgres psql

# In PostgreSQL shell, run these commands:
CREATE DATABASE myproject;
CREATE USER myprojectuser WITH PASSWORD 'strong_password_here';
ALTER ROLE myprojectuser SET client_encoding TO 'utf8';
ALTER ROLE myprojectuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE myprojectuser SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE myproject TO myprojectuser;
\q

Step 5: Clone Your Django Project

# Clone your repository
cd /home/django
git clone https://github.com/yourusername/yourproject.git
cd yourproject

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

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

Step 6: Configure Django Settings for Production

Create a production settings file or update your existing settings.py:

# settings.py or settings/production.py

import os
from pathlib import Path

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

ALLOWED_HOSTS = ['your-domain.com', 'www.your-domain.com', 'your_server_ip']

# Database
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'myproject',
        'USER': 'myprojectuser',
        'PASSWORD': os.environ.get('DB_PASSWORD'),
        'HOST': 'localhost',
        'PORT': '5432',
    }
}

# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

# Security settings
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'

Set Environment Variables

# Create .env file
nano /home/django/yourproject/.env

# Add these variables:
DJANGO_SECRET_KEY=your_secret_key_here
DB_PASSWORD=strong_password_here
DJANGO_SETTINGS_MODULE=yourproject.settings.production

Run Migrations and Collect Static Files

# Make sure you're in virtual environment
source venv/bin/activate

# Load environment variables
export $(cat .env | xargs)

# Run migrations
python manage.py migrate

# Create superuser
python manage.py createsuperuser

# Collect static files
python manage.py collectstatic --no-input

Step 7: Configure Gunicorn

Test Gunicorn

# Test Gunicorn (replace 'yourproject' with your project name)
gunicorn --bind 0.0.0.0:8000 yourproject.wsgi:application

# Press Ctrl+C to stop after testing

Create Gunicorn systemd Service

sudo nano /etc/systemd/system/gunicorn.service

Add this configuration:

[Unit]
Description=gunicorn daemon for Django project
After=network.target

[Service]
User=django
Group=www-data
WorkingDirectory=/home/django/yourproject
EnvironmentFile=/home/django/yourproject/.env
ExecStart=/home/django/yourproject/venv/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/home/django/yourproject/gunicorn.sock \
          yourproject.wsgi:application

[Install]
WantedBy=multi-user.target

Start and Enable Gunicorn

sudo systemctl start gunicorn
sudo systemctl enable gunicorn
sudo systemctl status gunicorn

Step 8: Configure Nginx

Create Nginx configuration:

sudo nano /etc/nginx/sites-available/yourproject

Add this configuration:

server {
    listen 80;
    server_name your-domain.com www.your-domain.com;

    location = /favicon.ico { access_log off; log_not_found off; }

    location /static/ {
        root /home/django/yourproject;
    }

    location /media/ {
        root /home/django/yourproject;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/home/django/yourproject/gunicorn.sock;
    }
}

Enable Nginx Site

# Create symlink
sudo ln -s /etc/nginx/sites-available/yourproject /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Restart Nginx
sudo systemctl restart nginx

# Allow Nginx through firewall
sudo ufw allow 'Nginx Full'

Step 9: Set Up SSL with Let's Encrypt

# Install Certbot
sudo apt install certbot python3-certbot-nginx -y

# Get SSL certificate
sudo certbot --nginx -d your-domain.com -d www.your-domain.com

# Test auto-renewal
sudo certbot renew --dry-run

🎉 Success!

Your Django app is now live at https://your-domain.com with free SSL!

Step 10: Set Up Monitoring & Logging

Configure Django Logging

Add to settings.py:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'file': {
            'level': 'ERROR',
            'class': 'logging.FileHandler',
            'filename': '/home/django/yourproject/logs/django.log',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['file'],
            'level': 'ERROR',
            'propagate': True,
        },
    },
}

Monitor Application Logs

# View Gunicorn logs
sudo journalctl -u gunicorn -f

# View Nginx access logs
sudo tail -f /var/log/nginx/access.log

# View Nginx error logs
sudo tail -f /var/log/nginx/error.log

Step 11: Set Up CI/CD with GitHub Actions

Create .github/workflows/deploy.yml in your repository:

name: Deploy to Production

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - name: Deploy to server
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.SERVER_IP }}
        username: django
        key: ${{ secrets.SSH_PRIVATE_KEY }}
        script: |
          cd /home/django/yourproject
          git pull origin main
          source venv/bin/activate
          pip install -r requirements.txt
          python manage.py migrate
          python manage.py collectstatic --no-input
          sudo systemctl restart gunicorn
          sudo systemctl restart nginx

Performance Optimization Tips

1. Enable Database Connection Pooling

pip install django-db-pool

# In settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django_db_pool.backends.postgresql',
        # ... rest of config
        'POOL_OPTIONS': {
            'MAX_SIZE': 20,
        }
    }
}

2. Set Up Redis for Caching

# Install Redis
sudo apt install redis-server -y
pip install django-redis

# In settings.py
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

3. Optimize Gunicorn Workers

Rule of thumb: (2 × CPU cores) + 1

# For 2 CPU cores:
--workers 5

# For 4 CPU cores:
--workers 9

Scaling Your Django App

Vertical Scaling (Easiest)

Upgrade your server size:

  • 1GB RAM → 2GB RAM: $6/mo → $12/mo
  • 2GB RAM → 4GB RAM: $12/mo → $24/mo
  • 4GB RAM → 8GB RAM: $24/mo → $48/mo

Horizontal Scaling (For High Traffic)

  1. Move PostgreSQL to a separate managed database
  2. Set up Redis for session storage
  3. Use object storage (S3, Spaces) for media files
  4. Deploy multiple app servers behind a load balancer
  5. Use CDN for static files

Common Issues & Solutions

502 Bad Gateway Error

# Check if Gunicorn is running
sudo systemctl status gunicorn

# Check socket file permissions
ls -l /home/django/yourproject/gunicorn.sock

# Check Nginx error logs
sudo tail -f /var/log/nginx/error.log

Static Files Not Loading

# Re-collect static files
python manage.py collectstatic --clear --no-input

# Check permissions
sudo chown -R django:www-data /home/django/yourproject/staticfiles

# Verify Nginx configuration
sudo nginx -t

Database Connection Errors

# Check PostgreSQL is running
sudo systemctl status postgresql

# Test database connection
sudo -u postgres psql -c "\l"

# Verify database credentials in .env file

Cost Comparison

ConfigurationVultrDigitalOceanBest For
1GB RAM, 1 vCPU$6/mo$6/moSmall apps, testing
2GB RAM, 1 vCPU$12/mo$12/moMost Django apps
4GB RAM, 2 vCPU$24/mo$24/moHigh traffic apps
8GB RAM, 4 vCPU$48/mo$48/moEnterprise apps

💰 Save Money:

With $300 in Vultr credits, you can run a production Django app (2GB RAM) for FREE for 25 months! That's over 2 years of free hosting.

Conclusion

You now have a production-ready Django deployment with:

  • ✓ Gunicorn for serving your Django app
  • ✓ Nginx as a reverse proxy
  • ✓ PostgreSQL database
  • ✓ Free SSL certificate
  • ✓ Systemd for process management
  • ✓ Monitoring and logging
  • ✓ CI/CD pipeline

This setup will handle thousands of requests per day. For higher traffic, follow the horizontal scaling steps above.

Next Steps:

  • Set up database backups with automated snapshots
  • Implement application monitoring (Sentry, New Relic)
  • Configure email sending (SendGrid, Mailgun)
  • Add Celery for background tasks
  • Set up staging environment

Ready to Deploy?

Get started with $500 in free credits from Vultr and DigitalOcean.


About this guide: This tutorial is updated for 2025 with the latest Django, Ubuntu, and deployment best practices. All commands have been tested on Ubuntu 24.04 LTS with Django 5.0.

Need help? Check out our other guides: