Skip to content

Instantly share code, notes, and snippets.

@FaridLU
Last active January 23, 2026 20:28
Show Gist options
  • Select an option

  • Save FaridLU/377f6adf3cdfd748d430f42e393417f7 to your computer and use it in GitHub Desktop.

Select an option

Save FaridLU/377f6adf3cdfd748d430f42e393417f7 to your computer and use it in GitHub Desktop.
Comprehensive Guide to Deploying Django on Ubuntu (18.04, 20.04, 22.04)

🚀 Deploy Django on Ubuntu with Gunicorn, Nginx, Celery & SSL

The Complete Production Deployment Guide for Django Applications

Ubuntu Django Python License


📖 Overview

This comprehensive guide walks you through deploying a production-ready Django application on Ubuntu. You'll learn to configure Gunicorn as the application server, Nginx as a reverse proxy, Celery for background tasks, Redis as a message broker, PostgreSQL as the database, and secure everything with a free SSL certificate from Let's Encrypt.

🏗️ What You'll Build

                    ┌───────────────────────────┐
                    │      INTERNET (WWW)       │
                    └─────────────┬─────────────┘
                                  │ HTTPS (443)
                                  ▼
                    ┌───────────────────────────┐
                    │    NGINX (Reverse Proxy)  │
                    │  SSL + Static Files       │
                    └─────────────┬─────────────┘
                                  │ Unix Socket
                                  ▼
                    ┌───────────────────────────┐
                    │  GUNICORN (WSGI Server)   │
                    │    Django Application     │
                    └──────┬─────────────┬──────┘
                           │             │
              ┌────────────┘             └────────────┐
              ▼                                       ▼
┌───────────────────────────┐         ┌───────────────────────────┐
│        POSTGRESQL         │         │      CELERY + REDIS       │
│         Database          │         │     Background Tasks      │
└───────────────────────────┘         └───────────────────────────┘

📑 Table of Contents


📋 Prerequisites

Before starting, ensure you have:

  • 🖥️ A Ubuntu server (18.04, 20.04, 22.04, or 24.04 LTS)
  • 🔐 Root or sudo access to the server
  • 🐍 A Django project ready for deployment
  • 💻 Basic familiarity with Linux command line
  • 🌐 A domain name (for SSL setup - optional for initial deployment)

⏱️ Estimated Time: 45-60 minutes for complete setup


🖥️ Part 1: Initial Server Setup

1.1 Create Your Server

Create a new Ubuntu server on your preferred cloud provider:

Choose a location closest to your target users for optimal latency.

1.2 Connect to Your Server

ssh root@your-server-ip-address

1.3 Create a Non-Root User

🛡️ Security Best Practice: Never run applications as root. Create a dedicated user for deployment.

# Create new user
adduser ubuntu

# Grant sudo privileges
usermod -aG sudo ubuntu

1.4 Configure Firewall

# Allow SSH connections
ufw allow OpenSSH

# Enable firewall
ufw enable

# Verify status
ufw status

1.5 Switch to New User

# From now on, connect as the new user
ssh ubuntu@your-server-ip-address

# Run commands with sudo when needed
sudo your-command

Checkpoint: You should now be able to SSH into your server as the new user.


🔑 Part 2: SSH Keys & Environment Configuration

2.1 Generate SSH Key for Git

# Generate SSH key
ssh-keygen -t ed25519 -C "your-email@example.com"

# Display public key (copy this)
cat ~/.ssh/id_ed25519.pub

💡 Note: If your system doesn't support Ed25519, use RSA instead:

ssh-keygen -t rsa -b 4096 -C "your-email@example.com"

2.2 Add SSH Key to GitHub/GitLab

  1. 📋 Copy the public key from the terminal
  2. 🔗 Navigate to your Git provider's SSH settings:
  3. ➕ Click "New SSH Key" and paste your key

2.3 Clone Your Project

# Clone repository
git clone git@github.com:username/your-project.git

# Navigate to project directory
cd your-project

2.4 Install System Dependencies

# Update package list
sudo apt update

# Install required packages
sudo apt install -y \
    python3-venv \
    python3-dev \
    libpq-dev \
    postgresql \
    postgresql-contrib \
    nginx \
    curl \
    build-essential \
    libssl-dev \
    libffi-dev

2.5 Set Up Python Virtual Environment

# Create virtual environment
python3 -m venv venv

# Activate virtual environment
source venv/bin/activate

# Upgrade pip
pip install --upgrade pip

# Install Gunicorn and PostgreSQL adapter
pip install gunicorn psycopg2-binary wheel

# Install project dependencies
pip install -r requirements.txt

2.6 Configure PostgreSQL Database 🐘

# Access PostgreSQL
sudo -u postgres psql

Run the following SQL commands:

-- Create database
CREATE DATABASE your_db_name;

-- Create user with secure password
CREATE USER your_db_user WITH PASSWORD 'your_secure_password';

-- Configure user settings
ALTER ROLE your_db_user SET client_encoding TO 'utf8';
ALTER ROLE your_db_user SET default_transaction_isolation TO 'read committed';
ALTER ROLE your_db_user SET timezone TO 'UTC';

-- Grant privileges
GRANT ALL PRIVILEGES ON DATABASE your_db_name TO your_db_user;

-- Exit PostgreSQL
\q

2.7 Configure Django Settings

Update your settings.py (or use environment variables):

# Database configuration
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'your_db_name',
        'USER': 'your_db_user',
        'PASSWORD': 'your_secure_password',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}

# Allowed hosts
ALLOWED_HOSTS = ['your-server-ip', 'yourdomain.com', 'www.yourdomain.com']

# Static files
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'static'

# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

🔐 Security Tip: Use environment variables for sensitive data:

import os
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')

2.8 Initialize Django Application

# Apply migrations
python manage.py migrate

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

# Create superuser (optional)
python manage.py createsuperuser

2.9 Test Development Server

# Allow port 8000 temporarily
sudo ufw allow 8000

# Run development server
python manage.py runserver 0.0.0.0:8000

🌐 Visit http://your-server-ip:8000 to verify the application works.

2.10 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, then deactivate the virtual environment:

deactivate

Checkpoint: Django app runs successfully with Gunicorn.


⚡ Part 3: Gunicorn & Nginx Setup

3.1 Create Gunicorn Socket File

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

Add the following content:

[Unit]
Description=Gunicorn socket for Django

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

3.2 Create Gunicorn Service File

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

Add the following content (update paths as needed):

[Unit]
Description=Gunicorn daemon for Django
Requires=gunicorn.socket
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/your-project
ExecStart=/home/ubuntu/your-project/venv/bin/gunicorn \
          --access-logfile - \
          --error-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn.sock \
          yourproject.wsgi:application

[Install]
WantedBy=multi-user.target

💡 Performance Tip: Set workers to (2 × CPU cores) + 1 for optimal performance.

3.3 Start and Enable Gunicorn

# Start Gunicorn socket
sudo systemctl start gunicorn.socket
sudo systemctl enable gunicorn.socket

# Verify socket is running
sudo systemctl status gunicorn.socket

# Check socket file exists
file /run/gunicorn.sock

3.4 Configure Nginx 🔧

Create a new Nginx configuration:

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

Add the following configuration:

server {
    listen 80;
    server_name your-server-ip yourdomain.com www.yourdomain.com;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

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

    # Static files
    location /static/ {
        alias /home/ubuntu/your-project/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Media files
    location /media/ {
        alias /home/ubuntu/your-project/media/;
        expires 7d;
    }

    # Proxy to Gunicorn
    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
        proxy_read_timeout 300;
        proxy_connect_timeout 300;
        proxy_send_timeout 300;
    }

    # Max upload size
    client_max_body_size 10M;
}

3.5 Enable the Site

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

# Test configuration
sudo nginx -t

# Restart Nginx
sudo systemctl restart nginx

3.6 Update Firewall Rules

# Remove temporary port 8000 rule
sudo ufw delete allow 8000

# Allow Nginx
sudo ufw allow 'Nginx Full'

3.7 Verify Deployment

🎉 Visit http://your-server-ip in your browser. Your Django application should be running!

Checkpoint: Django app is accessible via Nginx.


📦 Part 4: Celery, Redis & Supervisor

⏭️ Skip this section if your application doesn't use background tasks.

4.1 Install Redis and Supervisor

# Install Redis
sudo apt install -y redis-server

# Install Supervisor
sudo apt install -y supervisor

# Verify Redis is running
sudo systemctl status redis

4.2 Create Celery Worker Configuration 🥬

sudo nano /etc/supervisor/conf.d/celery_worker.conf

Add the following content:

[program:celeryworker]
command=/home/ubuntu/your-project/venv/bin/celery -A yourproject worker --loglevel=INFO
directory=/home/ubuntu/your-project
user=ubuntu
numprocs=1
stdout_logfile=/var/log/celery/worker.log
stderr_logfile=/var/log/celery/worker.log
autostart=true
autorestart=true
startsecs=10
stopwaitsecs=600
killasgroup=true
priority=998

4.3 Create Celery Beat Configuration (Optional) ⏰

For scheduled tasks:

sudo nano /etc/supervisor/conf.d/celery_beat.conf

Add the following content:

[program:celerybeat]
command=/home/ubuntu/your-project/venv/bin/celery -A yourproject beat --loglevel=INFO
directory=/home/ubuntu/your-project
user=ubuntu
numprocs=1
stdout_logfile=/var/log/celery/beat.log
stderr_logfile=/var/log/celery/beat.log
autostart=true
autorestart=true
startsecs=10
priority=999

4.4 Create Log Directory and Start Services

# Create log directory
sudo mkdir -p /var/log/celery
sudo chown ubuntu:ubuntu /var/log/celery

# Update Supervisor
sudo supervisorctl reread
sudo supervisorctl update

# Start services
sudo supervisorctl start all

# Check status
sudo supervisorctl status

4.5 Useful Supervisor Commands 📝

# Restart all services
sudo supervisorctl restart all

# View worker logs
sudo tail -f /var/log/celery/worker.log

# View beat logs
sudo tail -f /var/log/celery/beat.log

Checkpoint: Celery workers are running in the background.


🌐 Part 5: Domain & SSL Certificate

5.1 Point Domain to Server 🔗

Configure your domain's DNS settings:

  • A Record: @your-server-ip
  • A Record: wwwyour-server-ip

Note: DNS propagation can take up to 48 hours, but usually completes within minutes.

5.2 Update Nginx Configuration

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

Update the server_name directive:

server_name yourdomain.com www.yourdomain.com;

5.3 Update Django Settings

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

5.4 Reload Nginx

sudo nginx -t
sudo systemctl reload nginx

5.5 Install Certbot 🔒

# Install Certbot
sudo snap install --classic certbot

# Create symbolic link
sudo ln -s /snap/bin/certbot /usr/bin/certbot

5.6 Obtain SSL Certificate

sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Follow the prompts:

  1. 📧 Enter your email address
  2. ✅ Agree to terms of service (Y)
  3. 📰 Choose whether to share email with EFF

5.7 Verify Auto-Renewal

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

# Check renewal timer
sudo systemctl status snap.certbot.renew.timer

🎉 Your site is now accessible via https://yourdomain.com!

Checkpoint: SSL certificate installed and auto-renewal configured.


🔧 Troubleshooting

Common Issues and Solutions

502 Bad Gateway
  1. Check if Gunicorn is running:

    sudo systemctl status gunicorn
  2. Check Gunicorn logs:

    sudo journalctl -u gunicorn
  3. Verify socket file exists:

    file /run/gunicorn.sock
  4. Restart services:

    sudo systemctl daemon-reload
    sudo systemctl restart gunicorn
    sudo systemctl restart nginx
🖼️ Static Files Not Loading
  1. Verify STATIC_ROOT in settings.py
  2. Run python manage.py collectstatic
  3. Check Nginx configuration paths
  4. Verify file permissions:
    sudo chown -R ubuntu:www-data /home/ubuntu/your-project/static
🚫 Permission Denied Errors
  1. Check file ownership:

    ls -la /home/ubuntu/your-project
  2. Fix permissions:

    sudo chown -R ubuntu:www-data /home/ubuntu/your-project
    sudo chmod -R 755 /home/ubuntu/your-project
🐘 Database Connection Failed
  1. Verify PostgreSQL is running:

    sudo systemctl status postgresql
  2. Test database connection:

    sudo -u postgres psql -c "\l"
  3. Check credentials in settings.py

🥬 Celery Tasks Not Running
  1. Check Supervisor status:

    sudo supervisorctl status
  2. View Celery logs:

    sudo tail -f /var/log/celery/worker.log
  3. Restart Celery:

    sudo supervisorctl restart celeryworker

📚 Quick Reference

🔄 Service Management

Action Command
🔄 Restart Gunicorn sudo systemctl restart gunicorn
🔄 Restart Nginx sudo systemctl restart nginx
🔄 Restart Celery sudo supervisorctl restart all
📋 View Gunicorn logs sudo journalctl -u gunicorn -f
📋 View Nginx error log sudo tail -f /var/log/nginx/error.log
📋 View Celery logs sudo tail -f /var/log/celery/worker.log

✅ Deployment Checklist

  • 🖥️ Server created and SSH access configured
  • 👤 Non-root user created with sudo privileges
  • 🔥 Firewall configured (UFW)
  • 🔑 Git SSH key added to repository
  • 📦 Project cloned and dependencies installed
  • 🐘 PostgreSQL database configured
  • ⚙️ Django settings updated for production
  • 📁 Static files collected
  • 🦄 Gunicorn socket and service configured
  • 🔀 Nginx configured as reverse proxy
  • 🥬 Celery and Supervisor configured (if needed)
  • 🌐 Domain pointed to server
  • 🔒 SSL certificate installed
  • 🔄 Auto-renewal verified

🚀 Update Deployment

When you push new code:

cd ~/your-project
git pull origin main
source venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --noinput
sudo systemctl restart gunicorn
sudo supervisorctl restart all  # If using Celery
deactivate

🤝 Contributing

Found an issue or have a suggestion? Open an issue or submit a pull request.


📄 License

This guide is released under the MIT License.


⭐ If this guide helped you, please give it a star! ⭐

Made with ❤️ for the Django community

DigitalOcean Referral Badge

@pedromadureira000
Copy link

pedromadureira000 commented Aug 29, 2023

Remember to remove the unwanted AAAA registers. It may be used by Cerbot instead of your A register.
Let's Encrypt unauthorized 403 forbidden

@alok-38
Copy link

alok-38 commented Oct 17, 2023

Hello Farid,

Thank you for the awesome tutorial. This is really helpful.

I get this error message If I try to clone

ERROR: Repository not found.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

I will try to set up my own DB without cloning.

@FaridLU
Copy link
Author

FaridLU commented Oct 17, 2023

Hello Farid,

Thank you for the awesome tutorial. This is really helpful.

I get this error message If I try to clone

ERROR: Repository not found.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

I will try to set up my own DB without cloning.

Hi @alok-38 ,

I think the repository you are trying to access is a private repository. Make sure you can access the repository via your web browser. If yes, make sure you have added the SSH key properly in your GitHub profile by using the documentation above.

@Kazykan
Copy link

Kazykan commented Oct 20, 2023

3 дня пытался задеплоить на сервер. Только сегодня с помощью этой инструкции сделал это!!!

@FaridLU
Copy link
Author

FaridLU commented Oct 20, 2023

3 дня пытался задеплоить на сервер. Только сегодня с помощью этой инструкции сделал это!!!

Hi @Kazykan, the translation of your message is: "I tried to deploy to the server for 3 days. Only today, with the help of this instruction, I managed to do it!!!".

I am really glad to hear that the instruction was useful for you. 😅

@Roland-Szucs
Copy link

Hi @FaridLU ,

Really great tutorial and covers much more than the original Digital Ocean post. I am beginner in deployment especially on Ubuntu. When I see django/Apache/gunicorn stack to be deployed they change also onwerships and permissions to project directories, database directories adn set owners to WWW-DATA. If we use nginx it is not necessary?

@gunjanak
Copy link

/etc/systemd/system/gunicorn.socket:1: Assignment outside of section. Ignoring.
Help me I am getting this error

@FaridLU
Copy link
Author

FaridLU commented Feb 14, 2024

/etc/systemd/system/gunicorn.socket:1: Assignment outside of section. Ignoring. Help me I am getting this error

@gunjanak, can you please show me the gunicorn.socket file that you wrote? I think you've misaligned the config of that file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment