NewHalo Edge 3 is here: HTTP/3 everywhere and 42 edge locations.Learn more
Docs / LAMP and app hosting

Host a Python web app with Gunicorn

LAMP and app hosting10 min readUbuntu 24.04 LTS

Python web frameworks such as Flask and Django are not meant to face the internet directly. Gunicorn runs your app as a production-ready server, and nginx sits in front of it to handle visitors. This guide sets up a Flask example in a virtual environment, runs it as a service, and connects nginx to it.

Before you begin

  • A VPS running Ubuntu 24.04 LTS, with SSH access as a sudo user (this guide uses alex).
  • nginx installed (see Host a static website for the basics). The example uses the example.com domain.
  • Basic Python knowledge. Your own app can replace the example file.

Step 1: Install Python tools

Ubuntu 24.04 blocks pip from installing packages system-wide, to protect the operating system. The fix is a virtual environment: a private copy of Python for one project. The python3-venv package provides the tool that creates them.

sudo apt update
sudo apt install -y python3 python3-venv
Python 3.12.3
python3 --version

Step 2: Create the project and a virtual environment

Create a folder for the project, then create a virtual environment inside it. The source command activates the environment, so commands like pip use the project's copy of Python.

mkdir -p ~/apps/pyapp
cd ~/apps/pyapp
python3 -m venv venv
source venv/bin/activate
pip install flask gunicorn

Your prompt now starts with (venv), which means the environment is active. Packages you install here stay inside the venv folder.

Step 3: Write a small app

Create app.py. The file defines a variable named app, which Gunicorn will load.

nano app.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return "Hello from Gunicorn and Flask\n"

Test it with Gunicorn. The argument app:app means "the app variable inside the app.py module". Open a second SSH session and run curl, or check the output after you stop Gunicorn with Ctrl+C:

gunicorn --bind 127.0.0.1:8000 app:app
curl http://127.0.0.1:8000
Hello from Gunicorn and Flask

Step 4: Run Gunicorn as a service

Create a systemd service so the app starts on boot and restarts after a crash. Note the full path to Gunicorn inside the virtual environment, which is what makes the service use the right packages.

sudo nano /etc/systemd/system/pyapp.service
[Unit]
Description=Gunicorn for pyapp
After=network.target

[Service]
User=alex
WorkingDirectory=/home/alex/apps/pyapp
ExecStart=/home/alex/apps/pyapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 app:app
Restart=on-failure

[Install]
WantedBy=multi-user.target

The --workers 3 option runs three worker processes, so one slow request does not block everyone else. A common starting point is two workers per CPU core plus one. Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now pyapp
sudo systemctl status pyapp --no-pager
Active: active (running)

Step 5: Put nginx in front

nginx listens on the public port 80 and forwards requests to Gunicorn on 127.0.0.1:8000. This arrangement is called a reverse proxy.

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

    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, remove the default welcome site, and open the web ports in the firewall if they are not already open:

sudo ln -s /etc/nginx/sites-available/pyapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
sudo ufw allow 'Nginx Full'

Step 6: Test the whole path

curl -i http://example.com
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html; charset=utf-8

Hello from Gunicorn and Flask

Troubleshooting

  • "externally-managed-environment" when running pip. You are installing outside a virtual environment. Activate venv with source venv/bin/activate, then run pip again.
  • 502 Bad Gateway from nginx. Gunicorn is not running or not on port 8000. Check with sudo ss -tlnp | grep 8000 and sudo journalctl -u pyapp -n 50 --no-pager.
  • ModuleNotFoundError in the logs. The package was installed outside the venv. Activate it and run pip install again, then restart the service.
  • Changes to app.py do not show. Gunicorn loads code at startup. Run sudo systemctl restart pyapp.

Next steps

Add HTTPS so traffic is encrypted, and tune the number of workers as your traffic grows. The same reverse-proxy pattern works for Node.js apps, as shown in Host a Node.js app with systemd.

Something not working? Chat or email our support team and we will help you get unstuck.

Contact support