Websites & apps

Deploy a Python app with Gunicorn, systemd and Nginx

Run a Flask or Django app in production on a Linux VPS: virtualenv, Gunicorn workers, a systemd service, an Nginx reverse proxy and free SSL.

IntermediateLinux VPS3 min read

Python's built-in development servers are not meant for real traffic. The standard way to deploy a Python app is Gunicorn as the application server, systemd to keep it running and Nginx in front for HTTPS and static files. The same steps work for Flask, Django and FastAPI (with Uvicorn workers).

Before you start

  • An Ubuntu or Debian VPS with sudo access (commands for AlmaLinux are similar with dnf).
  • A domain pointing to the server's IP.
  • Your app in a Git repository with a requirements.txt.

Install packages and create a user

apt update && apt install python3-venv python3-pip nginx git
adduser --system --group --home /srv/myapp myapp

Running the app as its own unprivileged user limits damage if it is ever compromised.

Set up the app and virtualenv

sudo -u myapp git clone https://github.com/you/myapp.git /srv/myapp/app
sudo -u myapp python3 -m venv /srv/myapp/venv
sudo -u myapp /srv/myapp/venv/bin/pip install -r /srv/myapp/app/requirements.txt gunicorn

For Django, also run migrate and collectstatic, set DEBUG = False and add your domain to ALLOWED_HOSTS.

Test Gunicorn by hand (Flask app object app in app.py; for Django use myproject.wsgi:application):

cd /srv/myapp/app && sudo -u myapp ../venv/bin/gunicorn --bind 127.0.0.1:8000 app:app

Stop it with Ctrl+C once it responds to curl 127.0.0.1:8000.

Create a systemd service

Create /etc/systemd/system/myapp.service:

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

[Service]
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp/app
EnvironmentFile=/srv/myapp/app/.env
ExecStart=/srv/myapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 --access-logfile - app:app
Restart=on-failure

[Install]
WantedBy=multi-user.target

A common starting point is (2 × CPU cores) + 1 workers. For FastAPI add -k uvicorn.workers.UvicornWorker. Start it:

systemctl daemon-reload && systemctl enable --now myapp

Configure Nginx as a reverse proxy

Create /etc/nginx/sites-available/myapp:

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

    location /static/ {
        alias /srv/myapp/app/static/;
    }

    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 it and reload:

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

Add free SSL

apt install certbot python3-certbot-nginx && certbot --nginx -d example.com -d www.example.com

Certbot edits the Nginx config for HTTPS and sets up automatic renewal.

Deploy updates

cd /srv/myapp/app && sudo -u myapp git pull && sudo -u myapp ../venv/bin/pip install -r requirements.txt && systemctl restart myapp

Check that it works

  • systemctl status myapp shows active (running).
  • journalctl -u myapp -f streams Gunicorn logs.
  • Open https://example.com; static files should load from Nginx.

Troubleshooting

  • 502 Bad Gateway: Gunicorn is not running or listens on a different address. Check the service log and the proxy_pass port.
  • ModuleNotFoundError: the service is not using the virtualenv, or the WorkingDirectory is wrong.
  • WORKER TIMEOUT: a request runs longer than 30 seconds. Fix the slow code or raise --timeout.
  • Static files 404: the alias path must end with a slash and be readable by Nginx.

Python deployment FAQ

Gunicorn or uWSGI?

Gunicorn is simpler to configure and is the common default today. Both are production-ready.

Can I run Python apps on cPanel hosting?

Where the server offers it, cPanel → Software → Setup Python App (CloudLinux Python Selector) runs WSGI apps without root access.

Do I need Nginx in front of Gunicorn?

Yes in production: it handles TLS, slow clients and static files far better than Gunicorn alone.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.