Setting up your Django app for production might seem complex, but it doesn’t have to be. This step-by-step guide walks you through deploying a Django web application on Ubuntu, using Nginx as a reverse proxy, Gunicorn as the application server, and either MySQL or PostgreSQL as the database backend.
Whether you're a developer, DevOps engineer, or startup founder, this tutorial will walk you through each step with clarity and confidence.
Prerequisites
Before diving in, make sure you have the following:
- A cloud server (e.g., AWS EC2, DigitalOcean, Linode) running Ubuntu 22.04+
- A non-root user with
sudo
privileges - Python 3.8+ installed
- A Django project ready for deployment
- Domain name pointed to your server IP (optional but recommended)
- Basic understanding of Linux commands
Update the Server & Install Required Packages
update server
sudo apt update && sudo apt upgrade -y
install python
sudo apt install python3-pip python3-dev build-essential libssl-dev libffi-dev python3-venv curl git -y
Package | Description |
---|---|
python3-pip | Installs pip , the package manager for Python. You’ll use this to install Django, Gunicorn, and other Python libraries. |
python3-dev | Provides the header files needed to build Python extensions. Required when installing Python packages that include C extensions (e.g., mysqlclient ). |
build-essential | A meta-package that includes GCC, make, and other compilers/tools needed to build software from source. Often required when installing Python packages with native extensions. |
libssl-dev | SSL development libraries. Required for secure connections (e.g., when Python packages or apps use HTTPS, cryptography, etc.). |
libffi-dev | Foreign Function Interface library. Often used with cryptography and other security-related Python modules. |
python3-venv | Allows you to create isolated Python environments using python3 -m venv , which is essential for keeping your Django project dependencies clean and separate from system-wide Python. |
curl | A command-line tool for making HTTP requests. Useful for testing endpoints, downloading scripts, or getting files during setup. |
git | Version control system used to clone your Django project from GitHub or other repositories, and manage code changes. |
Create a Virtual Environment and Install Requirements
cd /opt/
sudo mkdir myproject && sudo chown $USER:$USER myproject
cd myproject
python3 -m venv venv
source venv/bin/activate
pip install django gunicorn mysqlclient
If you have a requirements.txt
file, use:
pip install -r requirements.txt
Why Use /opt/ Instead of /var/www/ for a Django Project?
Traditionally, /var/www/
is used to store static website files served directly by a web server like Apache. However, for modern web applications (like Django with Gunicorn and Nginx), that convention isn't strictly necessary or even ideal.
Reasons Developers Prefer /opt/
:
Reason | Explanation |
---|---|
Cleaner separation | /opt/ is designed for optional or third-party software — making it a good home for app-specific projects. |
Avoids permission issues | /var/www/ is typically owned by www-data , which can cause permission headaches during development and deployment. |
Less clutter | Keeps system files (/var ) separate from your custom applications. |
Easier to manage | You control ownership with sudo chown $USER:$USER , and tools like Git, pip, or virtualenv work seamlessly. |
Create a Non-Root User (if you don't have one)
sudo adduser --disabled-password --gecos "" django
Give User Access to Your Project Directory
sudo chown -R django:www-data /opt/myproject
Configure Django Settings for Production
Edit settings.py
:
- Set
DEBUG = False
- Add your domain to
ALLOWED_HOSTS
:
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
Collect static files:
python manage.py collectstatic
Run migrations:
python manage.py migrate
Create superuser (optional):
python manage.py createsuperuser
Set Up MySQL Database
Install MySQL and secure it:
sudo apt install mysql-server -y
sudo mysql_secure_installation
Development libraries for MySQL. Needed to compile and install mysqlclient for connecting Django to a MySQL database.
sudo apt install -y pkg-config libmysqlclient-dev -y
Access the MySQL shell:
sudo mysql
Create a database and user:
CREATE DATABASE myprojectdb CHARACTER SET UTF8;
CREATE USER 'myuser'@'localhost' IDENTIFIED BY 'strongpassword';
GRANT ALL PRIVILEGES ON myprojectdb.* TO 'myuser'@'localhost';
FLUSH PRIVILEGES;
myprojectdb
: Name of your Django project's database.myuser
: MySQL username Django will use to connect.strongpassword
: Replace with a secure password.
Tip: Always use strong passwords and unique usernames in production environments.
Update your Django settings.py
:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'myprojectdb',
'USER': 'myuser',
'PASSWORD': 'strongpassword',
'HOST': 'localhost',
'PORT': '3306',
}
}
Install MySQL client:
pip install mysqlclient
Run Gunicorn as the Application Server
Test Gunicorn manually first:
gunicorn --bind 0.0.0.0:8000 myproject.wsgi:application
If successful, create a systemd service for Gunicorn:
sudo nano /etc/systemd/system/gunicorn.service
Paste the following:
[Unit]
Description=gunicorn daemon
After=network.target
[Service]
# Replace with your actual Linux username (e.g. ubuntu)
User=django
Group=www-data
WorkingDirectory=/opt/myproject
# Use the correct WSGI module path (project name should match your Django structure)
ExecStart=/opt/myproject/venv/bin/gunicorn --workers 3 --bind unix:/opt/myproject/myproject.sock myproject.wsgi:application
[Install]
WantedBy=multi-user.target
Enable and start Gunicorn:
sudo systemctl daemon-reexec
sudo systemctl enable gunicorn
sudo systemctl start gunicorn
Configure Nginx as a Reverse Proxy
sudo apt install nginx -y
Package | Description |
---|---|
Nginx | A high-performance web server used to serve static files and act as a reverse proxy for Gunicorn or other app servers. |
Create a new config:
sudo nano /etc/nginx/sites-available/myproject
Paste:
server {
listen 80;
server_name example.com www.example.com;
# Disable logging for favicon
location = /favicon.ico { access_log off; log_not_found off; }
# Serve static files
location /static/ {
# replace with you django static files folder path
alias /opt/myproject/static/;
}
# Proxy all other requests to Gunicorn via Unix socket
location / {
include proxy_params;
proxy_pass http://unix:/opt/myproject/myproject.sock;
}
}
Enable the site:
sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled
sudo nginx -t
sudo systemctl restart nginx
Allow traffic on port 80:
sudo ufw allow 'Nginx Full'
Secure with HTTPS (Optional but Recommended)
Use Let’s Encrypt:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Set up auto-renewal:
sudo certbot renew --dry-run
Final Checklist
- Gunicorn is running as a service
- Nginx is correctly serving the Django app
- Static files are collected
- MySQL is connected and working
- HTTPS is enabled (optional)
- If Gunicorn shows a 500 error use this commad to inspect the logs: sudo journalctl -u gunicorn -e
Leave a comment
Your email address will not be published. Required fields are marked *