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ů.

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í.
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.
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).
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/.envKrok 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.
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 8000Krok 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.
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.comKrok 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.
# 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/appWorkery, 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.
# 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.