Slik kjører du Python-apper med Gunicorn og Nginx på en VPS
Deploy en Flask- eller FastAPI-applikasjon på Ubuntu med en virtualenv, Gunicorn systemd-tjeneste, Nginx reversproxy, TLS, miljøfiler, logging og en sjekkliste for 502-feil og worker-dimensjonering.

Den innebygde Flask-serveren og uvicorn --reload er for utvikling. På en offentlig VPS vil du ha en prosessbehandler som starter workers på nytt, binder til localhost og sitter bak en reversproxy som håndterer TLS og trege klienter. Gunicorn er det vanlige WSGI-valget for Flask og Django. FastAPI kan kjøre under Gunicorn med en Uvicorn-worker-klasse. Nginx (eller Caddy) avslutter HTTPS og videresender til 127.0.0.1:8000.
Denne veilederen går gjennom et oppsett som overlever omstarter: prosjekt i /srv/app, virtualenv, .env med hemmeligheter, en gunicorn.service-enhet, Nginx-serverblokk, Let's Encrypt og loggfiler du faktisk kan grep:e. Vi snakker også om worker-antall, tidsavbrudd og hvorfor 502 Bad Gateway nesten aldri er 'Nginx er ødelagt' — det er nesten alltid Gunicorn som ikke kjører, er bundet til feil socket, eller krasjer ved import. Eksempelet bruker Flask, med merknader for FastAPI der kommandoen skiller seg.
Hvorfor denne stakken
Gunicorn preforker worker-prosesser. Hver worker håndterer én forespørsel om gangen med mindre du bruker en annen worker-klasse. Nginx buffer trege klienter slik at workers ikke sitter fast med å sende byte til et mobilnett. systemd starter appen på nytt hvis den dør. Sammen er det kjedelig, som er det du vil ha klokken tre om natten.
- Gunicorn: stabil WSGI/ASGI-prosessmodell
- systemd: start ved oppstart, omstart ved krasj, journald-logger
- Nginx: TLS, statiske filer, grenser for forespørselsstørrelse, gzip
- venv: system-Python forblir rent
- localhost-binding: appen nås bare gjennom Nginx
Krav
Python 3.10+ på Ubuntu 22.04/24.04 er greit. Ikke kjør pip som root inn i systemets site-packages. Du trenger et domene for TLS. Hvis du foretrekker Caddy som proxy, forblir Gunicorn-enheten i denne artikkelen den samme — bare frontend-configen endres (se Caddy-artikkelen).
- Ubuntu 22.04- eller 24.04-VPS
- Applikasjonen din med en requirements.txt eller tilsvarende
- Et WSGI-inngangspunkt (for Flask: app:app) eller ASGI (for FastAPI: app:app med uvicorn-workers)
- Domene-A-post for HTTPS
Steg 1: Systempakker, bruker og prosjektkatalog
Opprett en systembruker som ikke kan logge inn interaktivt, eie koden og kjøre Gunicorn. Å installere python3-venv og byggeverktøy unngår pip-feil på pakker som fortsatt kompilerer C-utvidelser.
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/Steg 2: Virtualenv og avhengigheter
Opprett venv som appuser slik at fileierskapet blir riktig. Lås versjoner i produksjon. Etter installasjon, bekreft at du kan importere appen i en engangs gunicorn --check-config eller en Python-import. Importfeil her er de samme feilene som senere blir 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')"Steg 3: Miljøfil
Ikke hardkod SECRET_KEY eller database-URL-er i systemd-enheten på en måte som ender i verdenslesbar git. Bruk EnvironmentFile. chmod 640, eier root, gruppe appuser (eller eier appuser hvis du foretrekker det).
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/.envSteg 4: Gunicorn systemd-tjeneste
Bind til 127.0.0.1:8000, ikke 0.0.0.0, med mindre du har en grunn til å hoppe over Nginx. Worker-antall er ofte (2 x CPU) + 1 for synk-workers; på en 2 vCPU-VPS er det 5, som kan være for mange hvis hver worker laster en tung ML-modell — bruk da 2–3. For FastAPI, sett --worker-class uvicorn.workers.UvicornWorker og installer uvicorn. Tidsavbrudd skal overstige den tregeste ærlige forespørselen din, ikke 30 sekunder hvis du har 2-minutters eksporter.
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 8000Steg 5: Nginx reversproxy og TLS
Nginx lytter på 80/443 og proxer til Gunicorn. client_max_body_size betyr noe for opplastinger. proxy_read_timeout skal matche eller overstige Gunicorns timeout. Når serverblokken virker på HTTP, utsted et sertifikat med Certbot (eller bytt fronten til Caddy). Snippetet under er bare HTTP slik at du kan teste; kjør deretter Certbot som kan redigere filen.
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.comSteg 6: Statiske filer, rettigheter og Flask/FastAPI-spesifikke ting
Nginx bør servere statiske filer hvis du kan; det er raskere enn Gunicorn. Django collectstatic, Flask send_from_directory for små apper, eller en CDN senere. appuser må kunne lese treet. Hvis du bruker FastAPI, installer uvicorn[standard] i venv og endre ExecStart til å bruke UvicornWorker. Hvis du bruker Unix-sockets i stedet for TCP, pek proxy_pass mot socketen og match rettigheter slik at www-data kan skrive til den.
# 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, minne og null-nedetid-omlastinger
Synk-Gunicorn-workers er enkle og nok for CPU-lette request/response-API-er. Hvis du trenger mange samtidige trege I/O-ventinger, vurder gevent eller en ASGI-worker — mål, ikke gjett. Hver worker laster appen din; RAM ~= workers ganger app-RSS. En 2 GB-VPS med 8 workers av en 300 MB-app vil swappe og føles 'tilfeldig treg'. systemctl reload gunicorn (HUP) kan starte workers på nytt med den nye koden hvis du deployet filer på plass; en full omstart er tydeligere når avhengigheter endres.
# 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/healthFeilsøking av 502 og stille krasj
502 betyr at Nginx ikke fikk et gyldig svar fra upstream. journalctl -u gunicorn -e er den første kommandoen. Vanlige årsaker: feil module:app-navn, manglende .env-nøkkel, Postgres kjører ikke, bundet til 127.0.0.1 men Nginx på en annen vert, SELinux (sjeldent på Ubuntu), eller appen lytter bare på IPv6. 504 er et tidsavbrudd. 301-løkker skjer når appen omdirigerer til HTTP mens X-Forwarded-Proto ignoreres.
- systemctl status gunicorn — er den aktiv?
- journalctl -u gunicorn -n 100 — ImportError, manglende env, database
- curl -v http://127.0.0.1:8000/ fra VPS-en — hvis dette feiler, er Nginx uskyldig
- nginx -t og error.log — upstream prematurely closed connection
- ss -tulpn | grep 8000 — ingenting lytter
- Disken full — workers krasjer på mystiske måter
Sikkerhet
Appen binder aldri offentlig. Hemmeligheter blir i .env. Hold venv og OS patchat. Ikke kjør Gunicorn som root. Hvis du håndterer innlogginger, sett øktinformasjonskapsler til Secure og SameSite, og konfigurer rammeverket til å stole på X-Forwarded-Proto bare fra Nginx. Hastighetsbegrens innloggingsruter i Nginx eller i appen.
- bind 127.0.0.1 eller en unix-socket
- chmod 600 .env
- Ikke-root User= i systemd
- TLS via Certbot eller Caddy
- Deaktiver debug-modus og auto-reload i produksjon
Tips
- Legg til en /health-rute som sjekker DB-tilkobling for Compose og lastbalanserere
- Send statiske ressurser med en cache-control-header i Nginx
- Bruk en separat worker eller kø (Redis + systemd) for e-post og tunge jobber
- Lås gunicorn- og uvicorn-versjoner i requirements.txt
- Ta et øyeblikksbilde før den første produksjonsovergangen
En Python-app på en VPS er produksjonsklar når den kjører under Gunicorn som en systemd-tjeneste, binder bare til localhost, og nås gjennom Nginx eller Caddy med TLS. Legg hemmeligheter i en EnvironmentFile, dimensjoner workers etter RAM i stedet for en bloggformel, og feilsøk 502 fra Gunicorn-journalen først. Når denne stien er dokumentert for repoet ditt, er hver senere deploy rsync eller git pull, pip install og systemctl restart gunicorn.