Setting up Django with PostgreSQL, Gunicorn, and Nginx on Ubuntu is essential for creating a secure, scalable, and production-ready environment.
This tutorial walks you through deploying a Django project on Ubuntu using:
- Python Virtual Environment
- PostgreSQL as the database
- Gunicorn as the WSGI application server
- Nginx as a reverse proxy
- Static and Media file configuration
Related - Setup Django with a Virtual Environment
Related - How to use Gunicorn with Django?
Whether you're starting fresh or deploying an existing Django project from a Git repository, this guide covers it all.
Prerequisites
- Ubuntu 22.04+
- A non-root user with sudo privileges
- Git installed
- Domain name configured (optional but recommended)
Clone Your Django Project Repository
If you're working with an existing Django project hosted on GitHub or another Git provider:
cd ~
git clone https://github.com/yourusername/yourproject.git
cd yourprojectSet Up a Python Virtual Environment
Why Use a Virtual Environment?
- Keeps your project dependencies isolated
- Avoids conflicts with system-level packages
- Essential for reproducibility and deployment
Commands:
sudo apt update
sudo apt install python3-pip python3-venv
python3 -m venv venv
source venv/bin/activatepip install --upgrade pip
pip install -r requirements.txt # if using a requirements filepip install django gunicorn psycopg2-binaryConfigure Your Django Settings
ALLOWED_HOSTS In settings.py, update this:
ALLOWED_HOSTS = ['yourdomain.com', 'your.server.ip.address']Configure PostgreSQL
Install PostgreSQL:
sudo apt install postgresql postgresql-contrib libpq-devsudo -u postgres psqlCREATE DATABASE yourdbname;
CREATE USER yourdbuser WITH PASSWORD 'yourdbpassword';
ALTER ROLE yourdbuser SET client_encoding TO 'utf8';
ALTER ROLE yourdbuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE yourdbuser SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE yourdbname TO yourdbuser;
\qDATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'yourdbname',
'USER': 'yourdbuser',
'PASSWORD': 'yourdbpassword',
'HOST': 'localhost',
'PORT': '5432',
}
}python manage.py migrateSet Up Static and Media Files
In settings.py:
import os
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')python manage.py collectstaticsudo chown -R yourusername:www-data static media
sudo chmod -R 775 static mediaRun and Test Gunicorn
Install:
pip install gunicorngunicorn --bind 127.0.0.1:8000 yourproject.wsgihttp://your.server.ip.address:8000Create a Gunicorn Systemd Service
sudo nano /etc/systemd/system/yourproject.service[Unit]
Description=gunicorn daemon for Django project
After=network.target
[Service]
User=yourusername
Group=www-data
WorkingDirectory=/home/yourusername/yourproject
ExecStart=/home/yourusername/yourproject/venv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/yourusername/yourproject/yourproject.sock yourproject.wsgi:application
[Install]
WantedBy=multi-user.targetsudo chown -R yourusername:www-data /home/yourusername/yourproject
sudo chmod -R 775 /home/yourusername/yourprojectsudo systemctl daemon-reexec
sudo systemctl start yourproject
sudo systemctl enable yourprojectSet Up Nginx
Install Nginx:
sudo apt install nginxsudo nano /etc/nginx/sites-available/yourprojectserver {
listen 80;
server_name yourdomain.com;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/yourusername/yourproject;
}
location /media/ {
root /home/yourusername/yourproject;
}
location / {
include proxy_params;
proxy_pass http://unix:/home/yourusername/yourproject/yourproject.sock;
}
}sudo ln -s /etc/nginx/sites-available/yourproject /etc/nginx/sites-enabled
sudo nginx -t
sudo systemctl restart nginxsudo ufw allow 'Nginx Full'Secure with HTTPS (Optional)
Install Certbot:
sudo apt install certbot python3-certbot-nginxsudo certbot --nginx -d yourdomain.comFinal Checklist
- ✅ Django served with Gunicorn
- ✅ PostgreSQL configured
- ✅ Static and media files served
- ✅ Nginx reverse proxy setup
- ✅ HTTPS enabled (optional)
- ✅ Permissions correctly set
- ✅ ALLOWED_HOSTS configured
- ✅ Gunicorn tested via browser
- ✅ Ready for production
FAQs
Do I need a virtual environment for Django?
Yes. It ensures isolated, reproducible environments for dependencies.
How does a virtual environment work?
It creates a self-contained directory with a specific Python interpreter and installed packages.
Are virtual environments portable?
Not directly. Use requirements.txt to replicate environments elsewhere.
Where are Python virtual environments stored?
Where you create them — typically inside your project (venv/).
Why Gunicorn?
It’s a production-grade WSGI HTTP server that integrates cleanly with Nginx.
Why Nginx?
To handle static files and reverse proxy HTTP requests to Gunicorn efficiently.
Which Django version?
Use the latest LTS or stable release (as of 2025, Django 5.0+ is recommended).
Check out: If you run into the django.db.utils.ProgrammingError: relation already exists issue while setting up PostgreSQL, this guide explains exactly how to resolve it.

