Zpět na blog
Srpen 19, 2026Návody

Jak spouštět Python aplikace s Gunicorn a Nginx na VPS

Nasaďte aplikaci Flask nebo FastAPI na Ubuntu s virtualenv, službou systemd Gunicorn, reverse proxy Nginx, TLS, soubory prostředí, logováním a kontrolním seznamem chyb 502 a dimenzování workerů.

Jak spouštět Python aplikace s Gunicorn a Nginx na VPS

Vestavěný server Flask a uvicorn --reload jsou pro vývoj. Na veřejném VPS chcete správce procesů, který restartuje workery, váže se na localhost a sedí za reverse proxy, které řeší TLS a pomalé klienty. Gunicorn je obvyklá volba WSGI pro Flask a Django. FastAPI může běžet pod Gunicorn s třídou workeru Uvicorn. Nginx (nebo Caddy) ukončí HTTPS a předá na 127.0.0.1:8000.

Tento návod vás provede rozložením, které přežije reboot: projekt v /srv/app, virtualenv, .env s tajemstvími, unit gunicorn.service, serverový blok Nginx, Let's Encrypt a logy, které opravdu umíte grepovat. Promluvíme i o počtu workerů, timeoutech a proč 502 Bad Gateway skoro nikdy neznamená „Nginx je rozbitý“ — skoro vždy Gunicorn neběží, je nabindovaný na špatný socket, nebo padá na importu. Příklad používá Flask, s poznámkami pro FastAPI tam, kde se příkaz liší.

Proč tento zásobník

Gunicorn preforkuje worker procesy. Každý worker obsluhuje jeden požadavek najednou, pokud nepoužijete jinou třídu workeru. Nginx bufferuje pomalé klienty, aby workery nezůstaly viset na posílání bajtů do mobilní sítě. systemd aplikaci restartuje, když zemře. Dohromady je to nuda — a přesně to chcete ve tři ráno.

  • Gunicorn: stabilní procesní model WSGI/ASGI
  • systemd: start při bootu, restart po pádu, logy journald
  • Nginx: TLS, statické soubory, limity velikosti požadavku, gzip
  • venv: systémový Python zůstane čistý
  • bind na localhost: k aplikaci se nedostanete jinak než přes Nginx

Požadavky

Python 3.10+ na Ubuntu 22.04/24.04 stačí. Nespouštějte pip jako root do systémových site-packages. Pro TLS potřebujete doménu. Pokud jako proxy preferujete Caddy, unit Gunicorn z tohoto článku zůstává stejný — mění se jen konfig frontendu (viz článek o Caddy).

  • VPS Ubuntu 22.04 nebo 24.04
  • Vaše aplikace s requirements.txt nebo ekvivalentem
  • Vstupní bod WSGI (pro Flask: app:app) nebo ASGI (pro FastAPI: app:app s workery uvicorn)
  • A záznam domény pro HTTPS

Krok 1. Systémové balíčky, uživatel a adresář projektu

Vytvořte systémového uživatele, který se nemůže interaktivně přihlásit, vlastní kód a spouští Gunicorn. Instalace python3-venv a build nástrojů zabrání selháním pip u balíčků, které ještě kompilují C rozšíření.

bash
ssh root@YOUR_VPS_IP
apt update && apt -y upgrade
apt -y install python3 python3-venv python3-pip python3-dev build-essential nginx curl

adduser --system --group --home /srv/app appuser
mkdir -p /srv/app
chown appuser:appuser /srv/app

# Copy your code (example):
# rsync -a --delete ./myproject/ appuser@YOUR_VPS_IP:/srv/app/

Krok 2. Virtualenv a závislosti

Venv vytvářejte jako appuser, ať vlastnictví souborů sedí. V produkci pinnujte verze. Po instalaci ověřte, že aplikaci umíte importovat jednorázovým gunicorn --check-config nebo importem v Pythonu. Chyby importu tady jsou stejné chyby, které se později stanou 502.

bash
sudo -u appuser -H bash -lc '
cd /srv/app
python3 -m venv /srv/app/venv
/srv/app/venv/bin/pip install --upgrade pip
/srv/app/venv/bin/pip install -r /srv/app/requirements.txt gunicorn
'

# Flask example check:
sudo -u appuser -H /srv/app/venv/bin/python -c "from app import app; print('import ok')"

Krok 3. Soubor prostředí

Nehardcodujte SECRET_KEY ani URL databáze v unitu systemd tak, aby skončily ve světově čitelném gitu. Použijte EnvironmentFile. chmod 640, vlastník root, skupina appuser (nebo vlastník appuser, pokud chcete).

bash
cat >/srv/app/.env <<'EOF'
FLASK_ENV=production
SECRET_KEY=replace-with-openssl-rand-hex-32
DATABASE_URL=postgresql://app:password@127.0.0.1:5432/app
EOF
chown appuser:appuser /srv/app/.env
chmod 600 /srv/app/.env

Krok 4. Služba systemd Gunicorn

Vážte se na 127.0.0.1:8000, ne na 0.0.0.0, pokud nemáte důvod Nginx obejít. Počet workerů je často (2 × CPU) + 1 u sync workerů; na VPS se 2 vCPU je to 5, což může být moc, pokud každý worker načítá těžký ML model — pak použijte 2–3. Pro FastAPI nastavte --worker-class uvicorn.workers.UvicornWorker a nainstalujte uvicorn. Timeouty by měly překročit nejpomalejší poctivý požadavek, ne 30 sekund, pokud máte exporty po 2 minutách.

bash
cat >/etc/systemd/system/gunicorn.service <<'EOF'
[Unit]
Description=Gunicorn for the web app
After=network.target

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

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now gunicorn
systemctl status gunicorn --no-pager
ss -tulpn | grep 8000

Krok 5. Reverse proxy Nginx a TLS

Nginx poslouchá na 80/443 a proxyuje na Gunicorn. client_max_body_size záleží u uploadů. proxy_read_timeout by měl sedět s timeoutem Gunicorn nebo ho převyšovat. Až serverový blok funguje na HTTP, vydejte certifikát Certbotem (nebo přepněte front na Caddy). Úryvek níže je jen HTTP, abyste mohli testovat; pak spusťte Certbot, který umí soubor upravit.

bash
cat >/etc/nginx/sites-available/app <<'EOF'
server {
    listen 80;
    server_name app.example.com;
    client_max_body_size 20m;

    location /static/ {
        alias /srv/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;
        proxy_read_timeout 90s;
    }
}
EOF

ln -sf /etc/nginx/sites-available/app /etc/nginx/sites-enabled/app
nginx -t && systemctl reload nginx

ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable

apt -y install certbot python3-certbot-nginx
certbot --nginx -d app.example.com

Krok 6. Statické soubory, práva a specifika Flask/FastAPI

Nginx by měl servírovat statiku, pokud můžete; je rychlejší než Gunicorn. Django collectstatic, Flask send_from_directory u drobných aplikací, nebo později CDN. appuser musí umět číst strom. Pokud používáte FastAPI, nainstalujte uvicorn[standard] do venv a změňte ExecStart na UvicornWorker. Pokud místo TCP použijete UNIX sockety, namiřte proxy_pass na socket a sladěte práva, aby na něj www-data mohlo zapisovat.

bash
# FastAPI ExecStart example:
# ExecStart=/srv/app/venv/bin/gunicorn -k uvicorn.workers.UvicornWorker --workers 2 --bind 127.0.0.1:8000 app:app

# Unix socket variant:
# --bind unix:/run/gunicorn/app.sock
# Nginx: proxy_pass http://unix:/run/gunicorn/app.sock:

chown -R appuser:appuser /srv/app

Workery, paměť a reload bez výpadku

Sync workery Gunicorn jsou jednoduché a stačí lehkým API požadavek/odpověď. Pokud potřebujete mnoho souběžných pomalých čekání I/O, zvažte gevent nebo ASGI worker — měřte, nehádejte. Každý worker načítá aplikaci; RAM ≈ počet workerů krát RSS aplikace. VPS 2 GB s 8 workery aplikace 300 MB půjde do swapu a bude „náhodně pomalý“. systemctl reload gunicorn (HUP) umí restartovat workery s novým kódem, pokud jste soubory nasadili na místě; plný restart je jasnější, když se mění závislosti.

bash
# After git pull / rsync of new code:
sudo -u appuser -H /srv/app/venv/bin/pip install -r /srv/app/requirements.txt
systemctl restart gunicorn
curl -I https://app.example.com/health

Řešení 502 a tichých pádů

502 znamená, že Nginx nedostal platnou odpověď od upstream. První příkaz je journalctl -u gunicorn -e. Časté příčiny: špatné jméno module:app, chybějící klíč v .env, Postgres neběží, bind na 127.0.0.1 ale Nginx na jiném hostu, SELinux (na Ubuntu vzácně), nebo aplikace poslouchá jen na IPv6. 504 je timeout. Smyčky 301 nastanou, když aplikace přesměrovává na HTTP a X-Forwarded-Proto se ignoruje.

  • systemctl status gunicorn — je active?
  • journalctl -u gunicorn -n 100 — ImportError, chybějící env, databáze
  • curl -v http://127.0.0.1:8000/ z VPS — pokud to selže, Nginx je nevinný
  • nginx -t a error.log — upstream prematurely closed connection
  • ss -tulpn | grep 8000 — nic neposlouchá
  • Disk plný — workery padají záhadnými způsoby

Bezpečnost

Aplikace se nikdy neveřejně neváže. Tajemství zůstanou v .env. Venv a OS záplatujte. Gunicorn nespouštějte jako root. Pokud řešíte přihlášení, nastavte session cookies Secure a SameSite a nakonfigurujte framework, aby X-Forwarded-Proto důvěřoval jen od Nginx. Limitování rychlosti přihlašovacích cest v Nginx nebo v aplikaci.

  • bind 127.0.0.1 nebo unix socket
  • chmod 600 .env
  • User= ne-root v systemd
  • TLS přes Certbot nebo Caddy
  • V produkci vypněte debug režim a auto-reload

Tipy

  • Přidejte cestu /health, která zkontroluje konektivitu DB pro Compose a load balancery
  • Statické assety posílejte s hlavičkou cache-control v Nginx
  • Pro e-maily a těžké úlohy použijte samostatný worker nebo frontu (Redis + systemd)
  • Pinnujte verze gunicorn a uvicorn v requirements.txt
  • Před prvním produkčním přepnutím udělejte snímek

Python aplikace na VPS je produkčně připravená, když běží pod Gunicorn jako služba systemd, váže se jen na localhost a je dosažitelná přes Nginx nebo Caddy s TLS. Tajemství dejte do EnvironmentFile, workery dimenzujte podle RAM, ne podle vzorce z blogu, a 502 nejdřív laděte z journalu Gunicorn. Až je tahle cesta zdokumentovaná pro vaše repo, každé další nasazení je rsync nebo git pull, pip install a systemctl restart gunicorn.