Skip to main content

Django – create and deploy a new Django app with Gunicorn and Nginx

Step-by-step guide to deploy Django on Ubuntu 22.04 using Gunicorn, Nginx, virtualenv, and optional PostgreSQL.

Andy Wallace avatar
Written by Andy Wallace
Updated over a month ago

This guide walks you through deploying a Django application on a LumaDock VPS running Ubuntu 22.04 LTS. You will configure Python, a virtual environment, Gunicorn, Nginx, and optional PostgreSQL and HTTPS.

Firewall note: if you enable the cloud firewall later, allow inbound ports 22/tcp, 80/tcp, and 443/tcp.

Requirements

A working Django setup requires:

  • A LumaDock VPS running Ubuntu 22.04 LTS

  • SSH access as root or a sudo-enabled user

  • A domain pointed to your VPS (recommended for HTTPS)

1) Connect and update

Log in:

ssh root@YOUR_SERVER_IP

Update packages:

apt update && apt -y upgrade

To avoid using root directly, create a sudo user:

adduser admin usermod -aG 

sudo admin ssh admin@YOUR_SERVER_IP


2) Install Python and build tools

Install required packages:

apt install -y python3 python3-venv python3-pip python3-dev build-essential nginx git


3) Create your Django project and virtual environment

Create a directory and activate a virtual environment:

mkdir -p /opt/djangoapp && 

cd /opt/djangoapp

python3 -m venv venv source

venv/bin/activate

Install Django and Gunicorn:

pip install --upgrade pip pip install django gunicorn

Create a Django project:

django-admin startproject myproject .

Allow your domain or IP in Django settings:

nano myproject/settings.py

# ALLOWED_HOSTS = ["YOUR_DOMAIN_OR_IP"]


4) Test with Gunicorn

Run migrations:

python manage.py migrate

Collect static files:

python manage.py collectstatic --noinput

Start Gunicorn:

gunicorn --bind 127.0.0.1:8000 myproject.wsgi

In another terminal:

curl -I http://127.0.0.1:8000

You should see 200 OK.


5) Set up Nginx as a reverse proxy

Create a new Nginx configuration:

nano /etc/nginx/sites-available/djangoapp

Paste the following block:

server {
listen 80;
server_name YOUR_DOMAIN_OR_IP;

client_max_body_size 20M;

location /static/ {
alias /opt/djangoapp/static/;
}

location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

Enable and test the configuration:

ln -s /etc/nginx/sites-available/djangoapp /etc/nginx/sites-enabled/ nginx -t && systemctl restart nginx

Visit:

http://YOUR_DOMAIN_OR_IP


6) Create a systemd service for Gunicorn

Create a service file:

nano /etc/systemd/system/gunicorn-djangoapp.service

Insert:

[Unit]
Description=Gunicorn for Django (djangoapp)
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/opt/djangoapp
Environment="PATH=/opt/djangoapp/venv/bin"
ExecStart=/opt/djangoapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 myproject.wsgi:application
Restart=always

[Install]
WantedBy=multi-user.target

Enable the service:

systemctl daemon-reload systemctl enable --now gunicorn-djangoapp 

systemctl status gunicorn-djangoapp


7) Optional — enable HTTPS with Let’s Encrypt

Install Certbot:

apt install -y certbot python3-certbot-nginx

Request a certificate:

certbot --nginx -d YOUR_DOMAIN -m you@example.com --agree-tos --redirect

Check automatic renewal:

systemctl list-timers | grep certbot


8) Optional — configure PostgreSQL

Install PostgreSQL:

apt install -y postgresql postgresql-contrib libpq-dev

Create a database and user:

sudo -u postgres psql

CREATE DATABASE mydb;
CREATE USER myuser WITH PASSWORD 'StrongPassword123!';
GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser;
\q

Install the driver:

source /opt/djangoapp/venv/bin/activate pip install psycopg2-binary

Update settings:

DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "mydb",
"USER": "myuser",
"PASSWORD": "StrongPassword123!",
"HOST": "127.0.0.1",
"PORT": "5432",
}
}

Apply migrations:

python manage.py migrate

systemctl restart gunicorn-djangoapp


9) Configure and collect static files

Add to settings:

STATIC_URL = "static/" 

STATIC_ROOT = "/opt/djangoapp/static"

Collect files and restart services:

python manage.py collectstatic --noinput

systemctl restart gunicorn-djangoapp

systemctl restart nginx


Troubleshooting

Common issues you may encounter:

  • 400 Bad Request: your domain/IP is missing from ALLOWED_HOSTS

  • 403/404 static files: STATIC_ROOT missing or static not collected

  • 502 Bad Gateway: Gunicorn is not running or Nginx proxy_pass uses wrong address

  • Port 80/443 errors: Nginx down or firewall blocking traffic


Appendix: useful commands

Logs and service checks:

journalctl -u gunicorn-djangoapp -e 

journalctl -u nginx -e

systemctl restart gunicorn-djangoapp

systemctl restart nginx

Check open ports:

ss -tulpen | egrep ':80|:443|:8000' curl -I http://127.0.0.1:8000 curl -I http://YOUR_DOMAIN_OR_IP

Your Django application is now running behind Nginx and Gunicorn on your LumaDock VPS.

You can deploy your code, configure environment variables, and continue building out your project.

Did this answer your question?