What You'll Deploy

This guide walks through deploying a Python web application (Flask or Django) to an Ubuntu 22.04 or 24.04 LTS VPS. The target architecture is production-ready: Gunicorn serves the WSGI app, systemd supervises the process, and Nginx handles TLS termination and static files. The same pattern works for any WSGI framework with minor adjustments.

Prepare the Server

Start with a freshly provisioned VPS. Update packages and install the Python runtime along with build tools for wheels that compile C extensions.

sudo apt update && sudo apt upgrade -y
sudo apt install python3 python3-venv python3-pip python3-dev \
    build-essential libpq-dev nginx git -y

Create a dedicated unprivileged user for the app. Never run web services as root.

sudo adduser --system --group --home /opt/myapp myapp
sudo mkdir -p /opt/myapp && sudo chown myapp:myapp /opt/myapp

Clone the Code and Create a Virtualenv

Switch to the app user and clone the repository. Using a virtualenv isolates dependencies from the system Python and makes upgrades predictable.

sudo -u myapp -H bash
cd /opt/myapp
git clone https://github.com/yourorg/yourapp.git app
cd app
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
pip install gunicorn

Populate a .env file with database URLs, secret keys, and any third-party credentials. Keep it mode 600 and never commit it.

Run the App with Gunicorn

Gunicorn is a pre-fork WSGI server that handles concurrency well on a single VPS. A reasonable starting point is 2 * CPU_cores + 1 workers. Test the app manually before adding systemd:

cd /opt/myapp/app
source venv/bin/activate
gunicorn --bind 127.0.0.1:8000 --workers 3 myapp.wsgi:application

For Django the module path is usually projectname.wsgi:application. For Flask it's app:app or wsgi:app depending on your entry point. Confirm the site responds with curl http://127.0.0.1:8000.

Supervise Gunicorn with systemd

Create a service unit so Gunicorn starts at boot, restarts on failure, and logs to the journal. As root, write /etc/systemd/system/myapp.service:

[Unit]
Description=MyApp Gunicorn Service
After=network.target

[Service]
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp/app
EnvironmentFile=/opt/myapp/app/.env
ExecStart=/opt/myapp/app/venv/bin/gunicorn \
    --workers 3 \
    --bind unix:/run/myapp.sock \
    --access-logfile - \
    myapp.wsgi:application
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Reload systemd and enable the service:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp

Using a Unix socket instead of a TCP port removes network overhead and tightens security — only local processes can connect.

Configure Nginx as a Reverse Proxy

Nginx handles TLS, serves static files, and forwards requests to Gunicorn. Create /etc/nginx/sites-available/myapp:

server {
    listen 80;
    server_name example.com www.example.com;

    location /static/ {
        alias /opt/myapp/app/static/;
        expires 30d;
    }

    location / {
        proxy_pass http://unix:/run/myapp.sock;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Enable the site and reload Nginx:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

For a deeper dive on proxy tuning see our guide on Nginx reverse proxy configuration.

Add HTTPS with Let's Encrypt

Certbot automates certificate issuance and renewal. Point your domain's A record at the VPS first, then run:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com

Certbot rewrites the Nginx config to listen on 443 and installs a renewal cron. For commercial or EV certificates, browse our SSL certificate options. You can also follow our detailed SSL install tutorial.

Database Migrations and Static Files

Finish the deployment with framework-specific housekeeping. For Django:

cd /opt/myapp/app
source venv/bin/activate
python manage.py migrate
python manage.py collectstatic --noinput

For Flask with Flask-Migrate, run flask db upgrade. Restart the app so new code takes effect:

sudo systemctl restart myapp

Deployment Best Practices

Deploying a Python app to production? MassiveGRID's Cloud VPS gives you NVMe storage, root access, and four global data centers for reliable Python hosting. See our developer solutions or contact our team to discuss your deployment.

Published by MassiveGRID, your trusted Linux VPS hosting partner. Explore our Cloud VPS plans for root-access Ubuntu hosting.