Deploying Python Web Apps

· 4 min read · Updated March 17, 2026 · intermediate
deployment production gunicorn nginx ssl devops

Deploying a Python web application to production requires several steps beyond development. Your app needs a WSGI server, a reverse proxy, SSL certificates, and proper configuration. This tutorial covers each component and shows you how to deploy with confidence.

Choosing a Hosting Platform

Your deployment choice depends on budget, scalability needs, and operational complexity.

Virtual Private Servers (VPS) VPS providers give you full control over the server. You manage everything: OS, software, security updates. Popular options include DigitalOcean, Linode, Hetzner, and AWS EC2. This approach offers maximum flexibility but requires sysadmin skills.

Platform as a Service (PaaS) Platforms like Render, Heroku, Railway, and Fly.io abstract server management. You push code, they handle the infrastructure. Simpler to start but more expensive at scale and less control over the environment.

Container Platforms Docker + Kubernetes or Docker Swarm gives you consistency across environments. Containers package your app with all dependencies. This approach scales well but has a learning curve.

For this tutorial, we assume a VPS with Ubuntu. The concepts apply elsewhere with minor adjustments.

Preparing Your Application for Production

Before deploying, update your configuration for production use.

Environment Variables

Never hardcode secrets in your code. Use environment variables for sensitive data.

import os

# Instead of: API_KEY = "secret123"
API_KEY = os.environ.get("API_KEY")

# With a default for development
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"

Create a .env file for local development (add it to .gitignore):

# .env
DEBUG=false
SECRET_KEY=your-secure-random-secret
DATABASE_URL=postgresql://user:pass@localhost/mydb

Requirements or Dependencies File

Pin your dependencies to specific versions:

pip freeze > requirements.txt

Or use pyproject.toml with modern tools like Poetry or uv:

[project]
dependencies = [
    "flask>=2.0,<3.0",
    "gunicorn>=20.0,<21.0",
]

Directory Structure

A typical production structure:

myapp/
├── app/
│   ├── __init__.py
│   └── routes.py
├── requirements.txt
├── .env
├── gunicorn.conf.py
└── start.sh

Using Gunicorn

Flask’s development server (app.run()) is not suitable for production. It handles one request at a time and isn’t secure.

Gunicorn is a battle-tested WSGI server. Install it:

pip install gunicorn

Run it:

gunicorn -w 4 -b 127.0.0.1:8000 "app:create_app()"
  • -w 4: Four worker processes (typically 2-4 × CPU cores)
  • -b: Bind address and port
  • "app:create_app()": Your application factory

Gunicorn Configuration

Create gunicorn.conf.py for production settings:

import multiprocessing

bind = "127.0.0.1:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
timeout = 30
keepalive = 2

# Logging
accesslog = "-"
errorlog = "-"
loglevel = "info"

# Process naming
proc_name = "myapp"

Test your configuration:

gunicorn -c gunicorn.conf.py "app:create_app()"

Setting Up Nginx as a Reverse Proxy

Nginx sits in front of Gunicorn, handling static files and forwarding dynamic requests.

Install Nginx:

sudo apt update
sudo apt install nginx

Create a site configuration:

# /etc/nginx/sites-available/myapp

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

    # Static files
    location /static/ {
        alias /path/to/myapp/static/;
    }

    # Media files
    location /media/ {
        alias /path/to/myapp/media/;
    }

    # Proxy to Gunicorn
    location / {
        proxy_pass http://127.0.0.1:8000;
        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:

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

Securing with SSL/HTTPS

Let’s Encrypt provides free certificates. Install Certbot:

sudo apt install certbot python3-certbot-nginx

Obtain and install the certificate:

sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot automatically configures HTTPS and sets up auto-renewal. Test renewal:

sudo certbot renew --dry-run

Your site now serves over HTTPS with automatic certificate renewal.

Systemd Service

Create a systemd service to manage your application:

# /etc/systemd/system/myapp.service

[Unit]
Description=Gunicorn instance for myapp
After=network.target

[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/path/to/myapp
Environment="PATH=/path/to/myapp/venv/bin"
ExecStart=/path/to/myapp/venv/bin/gunicorn -c gunicorn.conf.py "app:create_app()"
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Enable and start:

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

Basic Deployment Automation

Manual deployments are error-prone. Use a simple deployment script:

#!/bin/bash
# deploy.sh

set -e

echo "Pulling latest code..."
cd /path/to/myapp
git pull origin main

echo "Installing dependencies..."
source venv/bin/activate
pip install -r requirements.txt

echo "Running migrations..."
flask db upgrade  # or your migration command

echo "Restarting application..."
sudo systemctl restart myapp

echo "Deployment complete!"

Make it executable:

chmod +x deploy.sh

For more advanced deployments, consider tools like Fabric, Invoke, or Ansible.

Monitoring Basics

After deployment, monitor your application.

Check Application Status

sudo systemctl status myapp
journalctl -u myapp -f  # Follow logs

Health Check Endpoint

Add a simple health check to your app:

@app.route("/health")
def health():
    return {"status": "ok"}

Nginx Status

Enable Nginx status page for monitoring:

location /nginx-status {
    stub_status on;
    allow 127.0.0.1;
    deny all;
}

Conclusion

You now understand the full deployment pipeline: WSGI server (Gunicorn), reverse proxy (Nginx), SSL (Let’s Encrypt), process management (systemd), and basic automation. These components work together to serve your Python application reliably in production.

For smaller projects, PaaS options like Render or Railway eliminate much of this complexity. As your needs grow, the VPS approach gives you control and scalability. Start simple, iterate, and learn from each deployment.