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 @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?