The Complete Production Deployment Guide for Django Applications
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.
┌───────────────────────────┐
│ INTERNET (WWW) │
└─────────────┬─────────────┘
│ HTTPS (443)
▼
┌───────────────────────────┐
│ NGINX (Reverse Proxy) │
│ SSL + Static Files │
└─────────────┬─────────────┘
│ Unix Socket
▼
┌───────────────────────────┐
│ GUNICORN (WSGI Server) │
│ Django Application │
└──────┬─────────────┬──────┘
│ │
┌────────────┘ └────────────┐
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ POSTGRESQL │ │ CELERY + REDIS │
│ Database │ │ Background Tasks │
└───────────────────────────┘ └───────────────────────────┘
- 📋 Prerequisites
- 🖥️ Part 1: Initial Server Setup
- 🔑 Part 2: SSH Keys & Environment Configuration
- ⚡ Part 3: Gunicorn & Nginx Setup
- 📦 Part 4: Celery, Redis & Supervisor
- 🌐 Part 5: Domain & SSL Certificate
- 🔧 Troubleshooting
- 📚 Quick Reference
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
Create a new Ubuntu server on your preferred cloud provider:
- ☁️ DigitalOcean
- ☁️ AWS EC2
- ☁️ Linode
- ☁️ Vultr
Choose a location closest to your target users for optimal latency.
ssh root@your-server-ip-address🛡️ 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# Allow SSH connections
ufw allow OpenSSH
# Enable firewall
ufw enable
# Verify status
ufw status# 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.
# 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"
- 📋 Copy the public key from the terminal
- 🔗 Navigate to your Git provider's SSH settings:
- GitHub: github.com/settings/keys
- GitLab: gitlab.com/-/profile/keys
- ➕ Click "New SSH Key" and paste your key
# Clone repository
git clone git@github.com:username/your-project.git
# Navigate to project directory
cd your-project# 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# 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# Access PostgreSQL
sudo -u postgres psqlRun 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
\qUpdate 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')
# Apply migrations
python manage.py migrate
# Collect static files
python manage.py collectstatic --noinput
# Create superuser (optional)
python manage.py createsuperuser# 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.
# Test Gunicorn (replace 'yourproject' with your project name)
gunicorn --bind 0.0.0.0:8000 yourproject.wsgi:applicationPress Ctrl+C to stop, then deactivate the virtual environment:
deactivate✅ Checkpoint: Django app runs successfully with Gunicorn.
sudo nano /etc/systemd/system/gunicorn.socketAdd the following content:
[Unit]
Description=Gunicorn socket for Django
[Socket]
ListenStream=/run/gunicorn.sock
[Install]
WantedBy=sockets.targetsudo nano /etc/systemd/system/gunicorn.serviceAdd 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) + 1for optimal performance.
# 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.sockCreate a new Nginx configuration:
sudo nano /etc/nginx/sites-available/yourprojectAdd 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;
}# 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# Remove temporary port 8000 rule
sudo ufw delete allow 8000
# Allow Nginx
sudo ufw allow 'Nginx Full'🎉 Visit http://your-server-ip in your browser. Your Django application should be running!
✅ Checkpoint: Django app is accessible via Nginx.
⏭️ Skip this section if your application doesn't use background tasks.
# Install Redis
sudo apt install -y redis-server
# Install Supervisor
sudo apt install -y supervisor
# Verify Redis is running
sudo systemctl status redissudo nano /etc/supervisor/conf.d/celery_worker.confAdd 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=998For scheduled tasks:
sudo nano /etc/supervisor/conf.d/celery_beat.confAdd 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# 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# 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.
Configure your domain's DNS settings:
- A Record:
@→your-server-ip - A Record:
www→your-server-ip
⏳ Note: DNS propagation can take up to 48 hours, but usually completes within minutes.
sudo nano /etc/nginx/sites-available/yourprojectUpdate the server_name directive:
server_name yourdomain.com www.yourdomain.com;ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']sudo nginx -t
sudo systemctl reload nginx# Install Certbot
sudo snap install --classic certbot
# Create symbolic link
sudo ln -s /snap/bin/certbot /usr/bin/certbotsudo certbot --nginx -d yourdomain.com -d www.yourdomain.comFollow the prompts:
- 📧 Enter your email address
- ✅ Agree to terms of service (Y)
- 📰 Choose whether to share email with EFF
# 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.
❌ 502 Bad Gateway
-
Check if Gunicorn is running:
sudo systemctl status gunicorn
-
Check Gunicorn logs:
sudo journalctl -u gunicorn
-
Verify socket file exists:
file /run/gunicorn.sock
-
Restart services:
sudo systemctl daemon-reload sudo systemctl restart gunicorn sudo systemctl restart nginx
🖼️ Static Files Not Loading
- Verify
STATIC_ROOTin settings.py - Run
python manage.py collectstatic - Check Nginx configuration paths
- Verify file permissions:
sudo chown -R ubuntu:www-data /home/ubuntu/your-project/static
🚫 Permission Denied Errors
-
Check file ownership:
ls -la /home/ubuntu/your-project
-
Fix permissions:
sudo chown -R ubuntu:www-data /home/ubuntu/your-project sudo chmod -R 755 /home/ubuntu/your-project
🐘 Database Connection Failed
-
Verify PostgreSQL is running:
sudo systemctl status postgresql
-
Test database connection:
sudo -u postgres psql -c "\l" -
Check credentials in settings.py
🥬 Celery Tasks Not Running
-
Check Supervisor status:
sudo supervisorctl status
-
View Celery logs:
sudo tail -f /var/log/celery/worker.log
-
Restart Celery:
sudo supervisorctl restart celeryworker
| 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 |
- 🖥️ 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
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
deactivateFound an issue or have a suggestion? Open an issue or submit a pull request.
This guide is released under the MIT License.
⭐ If this guide helped you, please give it a star! ⭐
Made with ❤️ for the Django community
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.