Deploying a Django Application on an Ubuntu VPS with Nginx, Gunicorn, and MySQL
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 - deploySet 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.pubAdd 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 -y3. 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 mysqlCREATE 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 mysqlclientCreate 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 .envDEBUG=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_dbApply your database migrations and collect your static assets:
python manage.py migrate
python manage.py collectstatic --no-input
python manage.py createsuperuser5. 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.serviceInsert 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.targetEnable and start the service:
sudo systemctl daemon-reload
sudo systemctl start gunicorn
sudo systemctl enable gunicorn
sudo systemctl status gunicorn6. 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/myappserver {
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/staticfilesActivate 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 nginx7. Firewall Hardening
Protect non-essential ports using Ubuntu's Uncomplicated Firewall (UFW):
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw --force enableYour Django web application is now active, isolated, and serving traffic through an optimized production web stack.