Rohan Yeole - HomepageRohan Yeole

How To Set Up Django with PostgreSQL, Gunicorn, and Nginx on Ubuntu (Production-Ready)

By Rohan Yeole

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 yourproject

Set 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/activate
Install dependencies:

pip install --upgrade pip
pip install -r requirements.txt # if using a requirements file
If you're starting fresh:

pip install django gunicorn psycopg2-binary

Configure 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-dev
Create the database and user:

sudo -u postgres psql
CREATE 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;
\q
Update settings.py:

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'yourdbname',
'USER': 'yourdbuser',
'PASSWORD': 'yourdbpassword',
'HOST': 'localhost',
'PORT': '5432',
}
}
Run migrations:

python manage.py migrate

Set 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/')
Run:

python manage.py collectstatic
Set permissions so Nginx and Gunicorn can access static/media files:

sudo chown -R yourusername:www-data static media
sudo chmod -R 775 static media

Run and Test Gunicorn

Install:

pip install gunicorn
Test locally:

gunicorn --bind 127.0.0.1:8000 yourproject.wsgi
Then go to your browser:

http://your.server.ip.address:8000
You should see your Django app running.

Create a Gunicorn Systemd Service

sudo nano /etc/systemd/system/yourproject.service
Paste:

[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.target
Permissions for socket access and file ownership:

sudo chown -R yourusername:www-data /home/yourusername/yourproject
sudo chmod -R 775 /home/yourusername/yourproject
Enable and start Gunicorn:

sudo systemctl daemon-reexec
sudo systemctl start yourproject
sudo systemctl enable yourproject

Set Up Nginx

Install Nginx:
sudo apt install nginx
Configure:
sudo nano /etc/nginx/sites-available/yourproject
Paste:
server {
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;
}
}
Enable and restart:

sudo ln -s /etc/nginx/sites-available/yourproject /etc/nginx/sites-enabled
sudo nginx -t
sudo systemctl restart nginx
Allow HTTP/S:

sudo ufw allow 'Nginx Full'

Secure with HTTPS (Optional)

Install Certbot:

sudo apt install certbot python3-certbot-nginx
Run:

sudo certbot --nginx -d yourdomain.com

Final 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).