Com executar aplicacions Python amb Gunicorn i Nginx en un VPS
Desplegueu una aplicació Flask o FastAPI a l'Ubuntu amb un virtualenv, un servei systemd de Gunicorn, un servidor intermediari invers Nginx, TLS, fitxers d'entorn, registres i una llista de comprovació per a errors 502 i dimensionament de workers.

El servidor Flask integrat i uvicorn --reload són per al desenvolupament. En un VPS públic voleu un gestor de processos que reiniciï workers, s'enllaci a localhost i s'assegui darrere d'un servidor intermediari invers que gestioni TLS i clients lents. Gunicorn és l'elecció WSGI habitual per a Flask i Django. FastAPI pot executar-se sota Gunicorn amb una classe de worker Uvicorn. Nginx (o Caddy) termina HTTPS i reenvia a 127.0.0.1:8000.
Aquesta guia recorre una disposició que sobreviu als reinicis: projecte a /srv/app, virtualenv, .env amb secrets, una unitat gunicorn.service, un bloc de servidor Nginx, Let's Encrypt i fitxers de registre que realment podeu grep. També parlarem del nombre de workers, els temps d'espera i per què 502 Bad Gateway gairebé mai no és 'Nginx està trencat' — gairebé sempre és Gunicorn que no s'executa, enllaçat al sòcol incorrecte o que es bloqueja a l'import. L'exemple fa servir Flask, amb notes per a FastAPI on la comanda difereix.
Per què aquesta pila
Gunicorn fa prefork de processos worker. Cada worker gestiona una sol·licitud alhora tret que feu servir una classe de worker diferent. Nginx amortitza els clients lents perquè els workers no es quedin encallats enviant bytes a una xarxa mòbil. systemd reinicia l'aplicació si mor. Junts això és avorrit, que és el que voleu a les 3 de la matinada.
- Gunicorn: model de procés WSGI/ASGI estable
- systemd: arrencada a l'engegada, reinici en cas de fallada, registres journald
- Nginx: TLS, fitxers estàtics, límits de mida de sol·licitud, gzip
- venv: el Python del sistema es queda net
- enllaç a localhost: l'aplicació no és accessible excepte a través de Nginx
Requisits
Python 3.10+ a Ubuntu 22.04/24.04 està bé. No executeu pip com a root als site-packages del sistema. Necessiteu un domini per a TLS. Si preferiu Caddy com a proxy, la unitat Gunicorn d'aquest article es queda igual — només canvia la configuració del frontal (vegeu l'article de Caddy).
- VPS Ubuntu 22.04 o 24.04
- La vostra aplicació amb un requirements.txt o equivalent
- Un punt d'entrada WSGI (per a Flask: app:app) o ASGI (per a FastAPI: app:app amb workers uvicorn)
- Registre A del domini per a HTTPS
Pas 1: Paquets del sistema, usuari i directori del projecte
Creeu un usuari de sistema que no pugui iniciar sessió de manera interactiva, sigui propietari del codi i executi Gunicorn. Instal·lar python3-venv i eines de compilació evita fallades de pip en paquets que encara compilen extensions C.
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/Pas 2: Virtualenv i dependències
Creeu el venv com a appuser perquè la propietat dels fitxers sigui correcta. Fixeu versions a producció. Després de la instal·lació, confirmeu que podeu importar l'aplicació en un gunicorn --check-config puntual o un import de Python. Els errors d'importació aquí són els mateixos errors que després es converteixen en 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')"Pas 3: Fitxer d'entorn
No incrusteu SECRET_KEY ni URL de base de dades a la unitat systemd d'una manera que acabi a git llegible per tothom. Utilitzeu EnvironmentFile. chmod 640, propietari root, grup appuser (o propietari appuser si ho preferiu).
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/.envPas 4: Servei systemd de Gunicorn
Enllaceu a 127.0.0.1:8000, no a 0.0.0.0, tret que tingueu un motiu per saltar-vos Nginx. El nombre de workers sovint és (2 x CPU) + 1 per a workers síncrons; en un VPS de 2 vCPU això són 5, que pot ser massa si cada worker carrega un model ML pesat — llavors feu servir 2–3. Per a FastAPI, establiu --worker-class uvicorn.workers.UvicornWorker i instal·leu uvicorn. Els temps d'espera han de superar la vostra sol·licitud honesta més lenta, no 30 segons si teniu exportacions de 2 minuts.
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 8000Pas 5: Servidor intermediari invers Nginx i TLS
Nginx escolta als 80/443 i fa proxy a Gunicorn. client_max_body_size importa per a les pujades. proxy_read_timeout ha de coincidir o superar el timeout de Gunicorn. Un cop el bloc de servidor funciona a HTTP, emeteu un certificat amb Certbot (o canvieu el frontal a Caddy). El fragment següent és només HTTP perquè pugueu provar; després executeu Certbot, que pot editar el fitxer.
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.comPas 6: Fitxers estàtics, permisos i especificitats de Flask/FastAPI
Nginx hauria de servir els fitxers estàtics si podeu; és més ràpid que Gunicorn. Django collectstatic, Flask send_from_directory per a aplicacions minúscules, o un CDN més endavant. L'appuser ha de poder llegir l'arbre. Si feu servir FastAPI, instal·leu uvicorn[standard] al venv i canvieu ExecStart per fer servir UvicornWorker. Si feu servir sòcols Unix en lloc de TCP, apunteu proxy_pass al sòcol i feu coincidir els permisos perquè www-data hi pugui escriure.
# 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/appWorkers, memòria i recàrregues sense temps d'inactivitat
Els workers síncrons de Gunicorn són simples i n'hi ha prou per a API de sol·licitud/resposta lleugeres de CPU. Si necessiteu moltes esperes d'E/S lentes concurrents, considereu gevent o un worker ASGI — mesureu, no endevineu. Cada worker carrega la vostra aplicació; RAM ~= workers per RSS de l'aplicació. Un VPS de 2 GB amb 8 workers d'una aplicació de 300 MB farà swap i se sentirà 'lent de manera aleatòria'. systemctl reload gunicorn (HUP) pot reiniciar workers amb el codi nou si heu desplegat fitxers al lloc; un reinici complet és més clar quan canvien les dependències.
# 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/healthResolució de 502 i bloquejos silenciosos
502 vol dir que Nginx no ha pogut obtenir una resposta vàlida de l'upstream. journalctl -u gunicorn -e és la primera comanda. Causes habituals: nom module:app incorrecte, clau .env que falta, Postgres no s'executa, enllaçat a 127.0.0.1 però Nginx en un altre amfitrió, SELinux (rar a l'Ubuntu), o l'aplicació escoltant només a IPv6. 504 és un temps d'espera. Els bucles 301 passen quan l'aplicació redirigeix a HTTP mentre s'ignora X-Forwarded-Proto.
- systemctl status gunicorn — està actiu?
- journalctl -u gunicorn -n 100 — ImportError, entorn que falta, base de dades
- curl -v http://127.0.0.1:8000/ des del VPS — si això falla, Nginx és innocent
- nginx -t i error.log — l'upstream ha tancat la connexió prematurament
- ss -tulpn | grep 8000 — no hi ha res escoltant
- Disc ple — els workers es bloquegen de maneres misterioses
Seguretat
L'aplicació mai no s'enllaça públicament. Els secrets es queden a .env. Mantingueu el venv i el sistema operatiu actualitzats. No executeu Gunicorn com a root. Si gestioneu inicis de sessió, establiu les galetes de sessió Secure i SameSite, i configureu el framework perquè confiï en X-Forwarded-Proto només des de Nginx. Limiteu la velocitat de les rutes d'inici de sessió a Nginx o a l'aplicació.
- enllaceu a 127.0.0.1 o a un sòcol unix
- chmod 600 .env
- User= no root a systemd
- TLS via Certbot o Caddy
- Desactiveu el mode de depuració i l'autorecàrrega a producció
Consells
- Afegiu una ruta /health que comprovi la connectivitat de la BD per a Compose i els equilibradors de càrrega
- Envieu actius estàtics amb una capçalera cache-control a Nginx
- Utilitzeu un worker o cua separats (Redis + systemd) per a correus i feines pesades
- Fixeu les versions de gunicorn i uvicorn a requirements.txt
- Feu una instantània abans del primer tall de producció
Una aplicació Python en un VPS està a punt per a producció quan s'executa sota Gunicorn com a servei systemd, s'enllaça només a localhost i s'hi accedeix a través de Nginx o Caddy amb TLS. Poseu els secrets en un EnvironmentFile, dimensioneu els workers segons la RAM en lloc d'una fórmula d'un article, i depureu el 502 primer des del diari de Gunicorn. Un cop aquest camí estigui documentat per al vostre repositori, cada desplegament posterior és rsync o git pull, pip install i systemctl restart gunicorn.