Tillbaka till bloggen
Augusti 19, 2026Guider

Hur du kör Python-appar med Gunicorn och Nginx på en VPS

Driftsätt en Flask- eller FastAPI-applikation på Ubuntu med en virtualenv, Gunicorn systemd-tjänst, Nginx omvänd proxy, TLS, miljöfiler, loggning och en checklista för 502-fel och worker-dimensionering.

Hur du kör Python-appar med Gunicorn och Nginx på en VPS

Den inbyggda Flask-servern och uvicorn --reload är för utveckling. På en publik VPS vill du ha en processhanterare som startar om workers, binder till localhost och sitter bakom en omvänd proxy som hanterar TLS och långsamma klienter. Gunicorn är det vanliga WSGI-valet för Flask och Django. FastAPI kan köras under Gunicorn med en Uvicorn-worker-klass. Nginx (eller Caddy) avslutar HTTPS och vidarebefordrar till 127.0.0.1:8000.

Den här guiden går igenom en layout som överlever omstarter: projekt i /srv/app, virtualenv, .env med hemligheter, en gunicorn.service-enhet, Nginx serverblock, Let's Encrypt och loggfiler du faktiskt kan grep:a. Vi pratar också om worker-antal, timeouts och varför 502 Bad Gateway nästan aldrig är 'Nginx är trasig' — det är nästan alltid Gunicorn som inte körs, är bunden till fel socket eller kraschar vid import. Exemplet använder Flask, med anteckningar för FastAPI där kommandot skiljer sig.

Varför den här stacken

Gunicorn preforkar worker-processer. Varje worker hanterar en begäran i taget om du inte använder en annan worker-klass. Nginx buffrar långsamma klienter så att workers inte fastnar med att skicka byte till ett mobilnät. systemd startar om appen om den dör. Tillsammans är det tråkigt, vilket är vad du vill ha klockan tre på natten.

  • Gunicorn: stabil WSGI/ASGI-processmodell
  • systemd: start vid boot, omstart vid krasch, journald-loggar
  • Nginx: TLS, statiska filer, gränser för begäransstorlek, gzip
  • venv: system-Python förblir rent
  • localhost-bindning: appen nås bara genom Nginx

Krav

Python 3.10+ på Ubuntu 22.04/24.04 duger. Kör inte pip som root in i systemets site-packages. Du behöver en domän för TLS. Om du föredrar Caddy som proxy förblir Gunicorn-enheten i den här artikeln densamma — bara frontend-configen ändras (se Caddy-artikeln).

  • Ubuntu 22.04- eller 24.04-VPS
  • Din applikation med en requirements.txt eller motsvarande
  • En WSGI-entrypoint (för Flask: app:app) eller ASGI (för FastAPI: app:app med uvicorn-workers)
  • Domän-A-post för HTTPS

Steg 1: Systempaket, användare och projektkatalog

Skapa en systemanvändare som inte kan logga in interaktivt, äg koden och kör Gunicorn. Att installera python3-venv och byggverktyg undviker pip-fel på paket som fortfarande kompilerar C-tillägg.

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/

Steg 2: Virtualenv och beroenden

Skapa venv som appuser så att filägandet blir rätt. Lås versioner i produktion. Efter installation, bekräfta att du kan importera appen i en engångs gunicorn --check-config eller en Python-import. Importfel här är samma fel som senare blir 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')"

Steg 3: Miljöfil

Hårdkoda inte SECRET_KEY eller databas-URL:er i systemd-enheten på ett sätt som hamnar i världsläslig git. Använd EnvironmentFile. chmod 640, ägare root, grupp appuser (eller ägare appuser om du föredrar det).

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

Steg 4: Gunicorn systemd-tjänst

Bind till 127.0.0.1:8000, inte 0.0.0.0, om du inte har en anledning att hoppa över Nginx. Worker-antal är ofta (2 x CPU) + 1 för synk-workers; på en 2 vCPU-VPS är det 5, vilket kan vara för många om varje worker laddar en tung ML-modell — använd då 2–3. För FastAPI, sätt --worker-class uvicorn.workers.UvicornWorker och installera uvicorn. Timeouts ska överstiga din långsammaste ärliga begäran, inte 30 sekunder om du har 2-minutersexporter.

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

Steg 5: Nginx omvänd proxy och TLS

Nginx lyssnar på 80/443 och proxysar till Gunicorn. client_max_body_size spelar roll för uppladdningar. proxy_read_timeout ska matcha eller överstiga Gunicorns timeout. När serverblocket fungerar på HTTP, utfärda ett certifikat med Certbot (eller byt fronten till Caddy). Snippeten nedan är bara HTTP så att du kan testa; kör sedan Certbot som kan redigera filen.

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

Steg 6: Statiska filer, behörigheter och Flask/FastAPI-specifika saker

Nginx bör serva statiska filer om du kan; det är snabbare än Gunicorn. Django collectstatic, Flask send_from_directory för små appar, eller en CDN senare. appuser måste kunna läsa trädet. Om du använder FastAPI, installera uvicorn[standard] i venv och ändra ExecStart till att använda UvicornWorker. Om du använder Unix-sockets i stället för TCP, peka proxy_pass mot socketen och matcha behörigheter så att www-data kan skriva till den.

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

Workers, minne och noll-downtime-omladdningar

Synk-Gunicorn-workers är enkla och räcker för CPU-lätta request/response-API:er. Om du behöver många samtidiga långsamma I/O-väntan, överväg gevent eller en ASGI-worker — mät, gissa inte. Varje worker laddar din app; RAM ~= workers gånger app-RSS. En 2 GB-VPS med 8 workers av en 300 MB-app kommer att swappa och kännas 'slumpmässigt långsam'. systemctl reload gunicorn (HUP) kan starta om workers med den nya koden om du driftssatte filer på plats; en full omstart är tydligare när beroenden ändras.

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

Felsökning av 502 och tysta krascher

502 betyder att Nginx inte kunde få ett giltigt svar från upstream. journalctl -u gunicorn -e är det första kommandot. Vanliga orsaker: fel module:app-namn, saknad .env-nyckel, Postgres körs inte, bunden till 127.0.0.1 men Nginx på en annan värd, SELinux (sällsynt på Ubuntu) eller appen lyssnar bara på IPv6. 504 är en timeout. 301-loopar händer när appen omdirigerar till HTTP medan X-Forwarded-Proto ignoreras.

  • systemctl status gunicorn — är den aktiv?
  • journalctl -u gunicorn -n 100 — ImportError, saknad env, databas
  • curl -v http://127.0.0.1:8000/ från VPS:en — om detta misslyckas är Nginx oskyldig
  • nginx -t och error.log — upstream prematurely closed connection
  • ss -tulpn | grep 8000 — ingenting lyssnar
  • Disken full — workers kraschar på mystiska sätt

Säkerhet

Appen binder aldrig publikt. Hemligheter stannar i .env. Håll venv och OS patchade. Kör inte Gunicorn som root. Om du hanterar inloggningar, sätt sessionscookies till Secure och SameSite, och konfigurera ramverket att lita på X-Forwarded-Proto bara från Nginx. Hastighetsbegränsa inloggningsrutter i Nginx eller i appen.

  • bind 127.0.0.1 eller en unix-socket
  • chmod 600 .env
  • Icke-root User= i systemd
  • TLS via Certbot eller Caddy
  • Inaktivera debug-läge och auto-reload i produktion

Tips

  • Lägg till en /health-rutt som kontrollerar DB-anslutning för Compose och lastbalanserare
  • Skeppa statiska tillgångar med en cache-control-header i Nginx
  • Använd en separat worker eller kö (Redis + systemd) för e-post och tunga jobb
  • Lås gunicorn- och uvicorn-versioner i requirements.txt
  • Ta en ögonblicksbild före den första produktionsövergången

En Python-app på en VPS är produktionsklar när den körs under Gunicorn som en systemd-tjänst, binder bara till localhost och nås genom Nginx eller Caddy med TLS. Lägg hemligheter i en EnvironmentFile, dimensionera workers efter RAM snarare än en bloggformel, och felsök 502 från Gunicorn-journalen först. När den här vägen är dokumenterad för ditt repo är varje senare deploy rsync eller git pull, pip install och systemctl restart gunicorn.