saulnyongesa4@gmail.com | +254708560861
Blog Post

Deploying a Django Application on an Ubuntu VPS with Nginx, Gunicorn, and MySQL

By Saul Mupalia Published Oct 25, 2026

A practical guide to setting up an Ubuntu server, configuring non-root deploy access, connecting MySQL, and running Django securely behind Gunicorn and Nginx.

Deploying a Django web application to a bare virtual private server gives you complete architectural control, predictable performance, and freedom from platform lock-in. In this guide, we walk through the exact foundation required to launch a production-grade Django application on an Ubuntu server using Gunicorn as the WSGI gateway, Nginx as the reverse proxy, and MySQL for persistence.

NOTE: The VPS used here is from interserver.net at a price of $ 3 per month. Click here to purchase, or text us, and we will guide you

1. Initial Server Setup and Deploy User

Never run a public web application as the root user. First, connect to your server and create a dedicated deploy user with administrative permissions:

adduser deploy
usermod -aG sudo deploy
su - deploy

Set up an SSH key pair so your server can pull repository files securely without prompting for passwords:

ssh-keygen -t ed25519 -C "deploy@server"
cat ~/.ssh/id_ed25519.pub

Add the printed public key to your Git hosting platform under deploy keys with read-only access.

2. Installing Core System Packages

Update system packages and install the Python build chain, Nginx, MySQL, and essential development headers:

sudo apt update && sudo apt upgrade -y
sudo apt install python3-pip python3-venv python3-dev build-essential libssl-dev libffi-dev pkg-config -y
sudo apt install mysql-server libmysqlclient-dev nginx git ufw -y

3. Database Provisioning

Log into MySQL to initialize your project database, create an application user, and grant scoped privileges. Always substitute placeholder names and generate strong passwords:

sudo mysql
CREATE DATABASE app_production_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'app_db_user'@'localhost' IDENTIFIED BY 'StrongRandomSecret987!';
GRANT ALL PRIVILEGES ON app_production_db.* TO 'app_db_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

4. Cloning the Project and Setting Up the Environment

Create a dedicated folder for your projects, clone your repository, and set up an isolated Python virtual environment:

mkdir -p ~/Projects && cd ~/Projects
git clone git@github.com:your-organization/your-django-repo.git myapp.com
cd myapp.com

python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
pip install gunicorn mysqlclient

Create a production .env file. When setting special characters such as the @ symbol in passwords within database URLs, remember to URL-encode them (for example, @ encodes to %40):

nano .env
DEBUG=False
SECRET_KEY=your-long-generated-secret-key-here
ALLOWED_HOSTS=203.0.113.195,example.com,www.example.com
DATABASE_URL=mysql://app_db_user:StrongRandomSecret987!@localhost:3306/app_production_db

Apply your database migrations and collect your static assets:

python manage.py migrate
python manage.py collectstatic --no-input
python manage.py createsuperuser

5. Configuring Gunicorn with Systemd

Systemd ensures Gunicorn starts automatically on system boot and restarts if an error occurs. Create a service file:

sudo nano /etc/systemd/system/gunicorn.service

Insert the following configuration, adjusting project folder paths to match your layout:

[Unit]
Description=Gunicorn daemon for Django Application
After=network.target

[Service]
User=deploy
Group=www-data
WorkingDirectory=/home/deploy/Projects/myapp.com
RuntimeDirectory=gunicorn
ExecStart=/home/deploy/Projects/myapp.com/venv/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn/gunicorn.sock \
          config.wsgi:application

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl start gunicorn
sudo systemctl enable gunicorn
sudo systemctl status gunicorn

6. Reverse Proxy Setup with Nginx

Nginx acts as a high-speed buffer, handling client SSL handshakes, caching, and serving static files directly from disk while forwarding dynamic requests to Gunicorn via the Unix socket.

sudo nano /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name 203.0.113.195 example.com www.example.com;

    client_max_body_size 25M;

    location = /favicon.ico { 
        access_log off; 
        log_not_found off; 
    }

    location /static/ {
        alias /home/deploy/Projects/myapp.com/staticfiles/;
    }

    location /media/ {
        alias /home/deploy/Projects/myapp.com/media/;
    }

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

Fix traversal permissions so the www-data user can read static files stored within the home directory:

sudo chmod 755 /home/deploy
sudo chmod 755 /home/deploy/Projects
sudo chmod 755 /home/deploy/Projects/myapp.com
sudo chmod -R 755 /home/deploy/Projects/myapp.com/staticfiles

Activate the site, test the Nginx configuration, and reload:

sudo ln -sf /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl restart nginx

7. Firewall Hardening

Protect non-essential ports using Ubuntu's Uncomplicated Firewall (UFW):

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw --force enable

Your Django web application is now active, isolated, and serving traffic through an optimized production web stack.

Back to All Blog Posts Home Overview