Web Development Tutorial

How To Set Up an ASGI Django App with Postgres, Nginx, and Uvicorn on Ubuntu 20.04

Step-by-step guide to deploy Django ASGI on Ubuntu 20.04 with PostgreSQL, Gunicorn+Uvicorn, systemd socket activation, and Nginx reverse proxy.

Drake Nguyen

Founder · System Architect

3 min read
How To Set Up an ASGI Django App with Postgres, Nginx, and Uvicorn on Ubuntu 20.04
How To Set Up an ASGI Django App with Postgres, Nginx, and Uvicorn on Ubuntu 20.04

Introduction

This guide shows how to deploy Django ASGI on Ubuntu 20.04 using PostgreSQL, Gunicorn with Uvicorn workers, and Nginx as a reverse proxy. It is aimed at developers and sysadmins preparing an Ubuntu 20.04 Django server for production. The instructions cover database setup, a Python virtual environment, configuring Gunicorn to run an ASGI application, creating systemd socket and service units, and configuring Nginx for proxying and static files.

Prerequisites

  • An Ubuntu 20.04 machine with a non-root sudo user.
  • Basic familiarity with the command line, Python, and Django.
  • A registered domain (recommended) or a static IP to set ALLOWED_HOSTS and Nginx server_name.

Step 1 — Install required packages

Update apt and install system packages needed for a standard django postgres nginx setup, development tools, and the PostgreSQL server.

sudo apt update
sudo apt install python3-venv libpq-dev postgresql postgresql-contrib nginx curl

Step 2 — Create PostgreSQL database and user

Use the postgres administrative account to create a database and a dedicated user for your Django project. Configure sensible defaults for encoding and timezone so Django works smoothly.

sudo -u postgres psql
-- inside psql
CREATE DATABASE myproject;
CREATE USER myprojectuser WITH PASSWORD 'strong_password';
ALTER ROLE myprojectuser SET client_encoding TO 'utf8';
ALTER ROLE myprojectuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE myprojectuser SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE myproject TO myprojectuser;
\q

Step 3 — Create and activate a Python virtual environment

Keep dependencies isolated by using a virtual environment. Install Django, Gunicorn, Uvicorn, and the Postgres adapter inside it.

mkdir ~/myprojectdir && cd ~/myprojectdir
python3 -m venv myprojectenv
source myprojectenv/bin/activate
pip install --upgrade pip
pip install django gunicorn uvicorn psycopg2-binary

Step 4 — Start a Django project and configure settings

Create the project and update settings for production use: set ALLOWED_HOSTS, configure PostgreSQL in DATABASES, and define STATIC_ROOT so Nginx can serve static files.

django-admin startproject myproject ~/myprojectdir
# edit ~/myprojectdir/myproject/settings.py
# set ALLOWED_HOSTS = ['your_domain_or_IP', 'localhost']
# DATABASES -> use 'django.db.backends.postgresql' with NAME, USER, PASSWORD, HOST='localhost'
# add at bottom:
STATIC_URL = '/static/'
import os
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

Initialize the database schema, create a superuser, and collect static files:

cd ~/myprojectdir
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
python manage.py collectstatic

Step 5 — Confirm Gunicorn can serve the ASGI app

Gunicorn can orchestrate Uvicorn workers for ASGI applications. Test this locally before wiring up systemd or Nginx.

source ~/myprojectdir/myprojectenv/bin/activate
cd ~/myprojectdir
gunicorn --bind 0.0.0.0:8000 myproject.asgi -w 4 -k uvicorn.workers.UvicornWorker

Alternatively, run Uvicorn directly for a minimal ASGI server:

uvicorn myproject.asgi:application --host 0.0.0.0 --port 8080

Step 6 — Create systemd socket and service for Gunicorn

Using systemd socket activation lets systemd open a Unix socket at boot and start Gunicorn on demand. Create two files: /etc/systemd/system/gunicorn.socket and /etc/systemd/system/gunicorn.service.

# /etc/systemd/system/gunicorn.socket
[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target
# /etc/systemd/system/gunicorn.service
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
User=youruser
Group=www-data
WorkingDirectory=/home/youruser/myprojectdir
ExecStart=/home/youruser/myprojectdir/myprojectenv/bin/gunicorn \
    --access-logfile - \
    -k uvicorn.workers.UvicornWorker \
    --workers 3 \
    --bind unix:/run/gunicorn.sock \
    myproject.asgi:application

[Install]
WantedBy=multi-user.target

Enable and start the socket so the socket file appears and systemd will handle activation:

sudo systemctl start gunicorn.socket
sudo systemctl enable gunicorn.socket

Step 7 — Verify the socket and activation

Check the socket status and the presence of the Unix socket. If socket activation is working, systemd will start Gunicorn when a connection hits the socket.

sudo systemctl status gunicorn.socket
file /run/gunicorn.sock
# trigger socket activation from the shell
curl --unix-socket /run/gunicorn.sock localhost
sudo systemctl status gunicorn

Step 8 — Configure Nginx as a reverse proxy

Create an Nginx server block that serves static files and proxies application requests to the Gunicorn Unix socket. This is a common pattern for django production deployment and nginx reverse proxy django setups.

sudo nano /etc/nginx/sites-available/myproject

# example server block
server {
    listen 80;
    server_name your_domain_or_IP;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/youruser/myprojectdir;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}

Enable and test Nginx, then open the firewall for web traffic:

sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled
sudo nginx -t
sudo systemctl restart nginx
sudo ufw allow 'Nginx Full'
# remove dev server rule if present
sudo ufw delete allow 8000

Step 9 — Common troubleshooting

If you see Nginx’s default page, confirm the server_name matches your domain or IP. For 502 errors, check both Nginx error logs and systemd/Gunicorn logs. Common checks include the socket path, file permissions, and that Gunicorn is actually running.

sudo tail -F /var/log/nginx/error.log
sudo journalctl -u gunicorn -b
# verify socket permissions
namei -l /run/gunicorn.sock

Also verify PostgreSQL is running if Django raises OperationalError about connecting to the database:

sudo systemctl status postgresql
sudo systemctl start postgresql
sudo systemctl enable postgresql

Conclusion

Following these steps provides a reproducible approach to deploy django asgi on ubuntu 20.04 with production-grade components: PostgreSQL for data storage, Gunicorn orchestrating Uvicorn ASGI workers, systemd socket activation to manage processes, and Nginx as a reverse proxy serving static files. Harden the server further by enabling TLS (Let's Encrypt) and tightening UFW rules before sending real traffic to this Ubuntu 20.04 Django server.

Tip: Use journalctl and Nginx logs together when diagnosing issues — they usually point directly to configuration mismatches such as socket paths, permissions, or database credentials.

Stay updated with Netalith

Get coding resources, product updates, and special offers directly in your inbox.