Înapoi la blog
August 19, 2026Ghiduri

Cum să rulezi aplicații Python cu Gunicorn și Nginx pe un VPS

Deplasează o aplicație Flask sau FastAPI pe Ubuntu cu un virtualenv, serviciu systemd Gunicorn, reverse proxy Nginx, TLS, fișiere de mediu, jurnalizare și o listă de verificare pentru erori 502 și dimensionarea workerilor.

Cum să rulezi aplicații Python cu Gunicorn și Nginx pe un VPS

Serverul Flask încorporat și uvicorn --reload sunt pentru dezvoltare. Pe un VPS public vrei un manager de procese care repornește workerii, se leagă de localhost și stă în spatele unui reverse proxy care gestionează TLS și clienții lenți. Gunicorn e alegerea WSGI obișnuită pentru Flask și Django. FastAPI poate rula sub Gunicorn cu o clasă de worker Uvicorn. Nginx (sau Caddy) termină HTTPS și înaintează către 127.0.0.1:8000.

Acest ghid trece printr-un aspect care supraviețuiește repornirilor: proiect în /srv/app, virtualenv, .env cu secrete, o unitate gunicorn.service, bloc de server Nginx, Let's Encrypt și fișiere de jurnal pe care le poți grep cu adevărat. Vom vorbi și despre numărul de workeri, timeout-uri și de ce 502 Bad Gateway aproape niciodată nu e «Nginx e stricat» — aproape întotdeauna Gunicorn nu rulează, e legat de socket-ul greșit sau crapă la import. Exemplul folosește Flask, cu note pentru FastAPI unde comanda diferă.

De ce acest teanc

Gunicorn pre-forkează procese worker. Fiecare worker gestionează o cerere pe rând decât dacă folosești o altă clasă de worker. Nginx bufferează clienții lenți ca workerii să nu rămână blocați trimițând octeți către o rețea mobilă. systemd repornește aplicația dacă moare. Împreună e plictisitor, adică ceea ce vrei la 3 dimineața.

  • Gunicorn: model de proces WSGI/ASGI stabil
  • systemd: pornire la boot, repornire la crash, jurnale journald
  • Nginx: TLS, fișiere statice, limite de dimensiune a cererii, gzip
  • venv: Python-ul de sistem rămâne curat
  • legare localhost: aplicația e accesibilă doar prin Nginx

Cerințe

Python 3.10+ pe Ubuntu 22.04/24.04 e în regulă. Nu rula pip ca root în site-packages-ul sistemului. Ai nevoie de un domeniu pentru TLS. Dacă preferi Caddy ca proxy, unitatea Gunicorn din acest articol rămâne aceeași — se schimbă doar configurația din față (vezi articolul Caddy).

  • VPS Ubuntu 22.04 sau 24.04
  • Aplicația ta cu un requirements.txt sau echivalent
  • Un punct de intrare WSGI (pentru Flask: app:app) sau ASGI (pentru FastAPI: app:app cu workeri uvicorn)
  • Înregistrare A de domeniu pentru HTTPS

Pasul 1: Pachete de sistem, utilizator și director de proiect

Creează un utilizator de sistem care nu se poate autentifica interactiv, deține codul și rulează Gunicorn. Instalarea python3-venv și a uneltelor de build evită eșecurile pip pe pachete care încă compilează extensii C.

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/

Pasul 2: Virtualenv și dependențe

Creează venv ca appuser ca proprietatea fișierelor să fie corectă. Fixează versiunile în producție. După instalare, confirmă că poți importa aplicația într-un gunicorn --check-config de unică folosință sau un import Python. Erorile de import aici sunt aceleași erori care mai târziu devin 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')"

Pasul 3: Fișier de mediu

Nu hardcoda SECRET_KEY sau URL-uri de bază în unitatea systemd într-un mod care ajunge în git citibil de toată lumea. Folosește EnvironmentFile. chmod 640, proprietar root, grup appuser (sau proprietar appuser dacă preferi).

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

Pasul 4: Serviciu systemd Gunicorn

Leagă de 127.0.0.1:8000, nu 0.0.0.0, decât dacă ai un motiv să sari peste Nginx. Numărul de workeri e adesea (2 x CPU) + 1 pentru workeri sincron; pe un VPS de 2 vCPU e 5, ceea ce poate fi prea mult dacă fiecare worker încarcă un model ML greu — atunci folosește 2–3. Pentru FastAPI, setează --worker-class uvicorn.workers.UvicornWorker și instalează uvicorn. Timeout-urile ar trebui să depășească cea mai lentă cerere onestă, nu 30 de secunde dacă ai exporturi de 2 minute.

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

Pasul 5: Reverse proxy Nginx și TLS

Nginx ascultă pe 80/443 și face proxy către Gunicorn. client_max_body_size contează pentru încărcări. proxy_read_timeout ar trebui să se potrivească sau să depășească timeout-ul Gunicorn. După ce blocul de server funcționează pe HTTP, emite un certificat cu Certbot (sau trece fața la Caddy). Fragmentul de mai jos e doar HTTP ca să poți testa; apoi rulează Certbot care poate edita fișierul.

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

Pasul 6: Fișiere statice, permisiuni și particularități Flask/FastAPI

Nginx ar trebui să servească fișierele statice dacă poți; e mai rapid decât Gunicorn. Django collectstatic, Flask send_from_directory pentru aplicații mici sau un CDN mai târziu. appuser trebuie să poată citi arborele. Dacă folosești FastAPI, instalează uvicorn[standard] în venv și schimbă ExecStart să folosească UvicornWorker. Dacă folosești socket-uri Unix în loc de TCP, îndreaptă proxy_pass către socket și potrivește permisiunile ca www-data să poată scrie pe el.

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

Workeri, memorie și reîncărcări fără downtime

Workerii Gunicorn sincron sunt simpli și suficienți pentru API-uri request/response ușoare pe CPU. Dacă ai nevoie de multe așteptări I/O lente concomitente, ia în calcul gevent sau un worker ASGI — măsoară, nu ghici. Fiecare worker încarcă aplicația ta; RAM ~= workeri ori RSS-ul aplicației. Un VPS de 2 GB cu 8 workeri ai unei aplicații de 300 MB va face swap și se va simți «lent la întâmplare». systemctl reload gunicorn (HUP) poate reporni workerii cu noul cod dacă ai deplasat fișierele pe loc; o repornire completă e mai clară când se schimbă dependențele.

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

Depanarea 502 și a crash-urilor tăcute

502 înseamnă că Nginx nu a putut obține un răspuns valid de la upstream. journalctl -u gunicorn -e e prima comandă. Cauze comune: nume module:app greșit, cheie .env lipsă, Postgres nu rulează, legat de 127.0.0.1 dar Nginx pe altă gazdă, SELinux (rar pe Ubuntu) sau aplicația ascultă doar IPv6. 504 e un timeout. Buclele 301 se întâmplă când aplicația redirecționează către HTTP în timp ce X-Forwarded-Proto e ignorat.

  • systemctl status gunicorn — e activ?
  • journalctl -u gunicorn -n 100 — ImportError, env lipsă, bază de date
  • curl -v http://127.0.0.1:8000/ de pe VPS — dacă asta eșuează, Nginx e nevinovat
  • nginx -t și error.log — upstream prematurely closed connection
  • ss -tulpn | grep 8000 — nimic nu ascultă
  • Disc plin — workerii crapă în feluri misterioase

Securitate

Aplicația nu se leagă niciodată public. Secretele rămân în .env. Ține venv-ul și OS-ul patch-uite. Nu rula Gunicorn ca root. Dacă gestionezi autentificări, setează cookie-urile de sesiune Secure și SameSite și configurează cadrul să aibă încredere în X-Forwarded-Proto doar de la Nginx. Limitează rata rutelor de autentificare în Nginx sau în aplicație.

  • bind 127.0.0.1 sau un socket unix
  • chmod 600 .env
  • User= non-root în systemd
  • TLS via Certbot sau Caddy
  • Dezactivează modul debug și auto-reload în producție

Sfaturi

  • Adaugă o rută /health care verifică conectivitatea DB pentru Compose și load balanceri
  • Livrează asset-uri statice cu un header cache-control în Nginx
  • Folosește un worker sau o coadă separată (Redis + systemd) pentru emailuri și joburi grele
  • Fixează versiunile gunicorn și uvicorn în requirements.txt
  • Fă un snapshot înainte de prima trecere în producție

O aplicație Python pe un VPS e gata de producție când rulează sub Gunicorn ca serviciu systemd, se leagă doar de localhost și e atinsă prin Nginx sau Caddy cu TLS. Pune secretele într-un EnvironmentFile, dimensionează workerii după RAM în loc de o formulă de blog și depanează 502 mai întâi din jurnalul Gunicorn. Odată ce această cale e documentată pentru repo-ul tău, fiecare deploy ulterior e rsync sau git pull, pip install și systemctl restart gunicorn.