Обратно към блога
Август 19, 2026Ръководства

Как да пускате Python приложения с Gunicorn и Nginx на VPS

Разгърнете приложение Flask или FastAPI на Ubuntu с virtualenv, услуга systemd за Gunicorn, обратен прокси Nginx, TLS, файлове на средата, журналиране и контролен списък за грешки 502 и оразмеряване на работници.

Как да пускате Python приложения с Gunicorn и Nginx на VPS

Вграденият сървър на Flask и uvicorn --reload са за разработка. На публичен VPS искате мениджър на процеси, който рестартира работници, се връзва към localhost и стои зад обратен прокси, който обработва TLS и бавни клиенти. Gunicorn е обичайният WSGI избор за Flask и Django. FastAPI може да върви под Gunicorn с клас работник Uvicorn. Nginx (или Caddy) сваля HTTPS и препраща към 127.0.0.1:8000.

Това ръководство минава през подредба, която оцелява рестарти: проект в /srv/app, virtualenv, .env с тайни, unit gunicorn.service, сървърен блок Nginx, Let's Encrypt и лог файлове, които наистина можете да grep-вате. Ще говорим и за брой работници, таймаути и защо 502 Bad Gateway почти никога не значи „Nginx е счупен“ — почти винаги Gunicorn не върви, е вързан към грешен сокет или пада при импорт. Примерът ползва Flask, с бележки за FastAPI там, където командата се различава.

Защо този стек

Gunicorn prefork-ва процеси работници. Всеки работник обработва по една заявка в даден момент, освен ако ползвате друг клас работник. Nginx буферира бавните клиенти, за да не засядат работниците в пращане на байтове към мобилна мрежа. systemd рестартира приложението, ако умре. Заедно това е скучно — а точно това искате в три през нощта.

  • Gunicorn: стабилен процесен модел WSGI/ASGI
  • systemd: старт при зареждане, рестарт при срив, логове journald
  • Nginx: TLS, статични файлове, лимити за размер на заявка, gzip
  • venv: системният Python остава чист
  • bind към localhost: до приложението се стига само през Nginx

Изисквания

Python 3.10+ на Ubuntu 22.04/24.04 стига. Не пускайте pip като root в системните site-packages. За TLS ви трябва домейн. Ако предпочитате Caddy като прокси, unit на Gunicorn от тази статия остава същият — сменя се само фронт конфигът (вижте статията за Caddy).

  • VPS Ubuntu 22.04 или 24.04
  • Вашето приложение с requirements.txt или еквивалент
  • Входна точка WSGI (за Flask: app:app) или ASGI (за FastAPI: app:app с работници uvicorn)
  • A запис на домейн за HTTPS

Стъпка 1. Системни пакети, потребител и каталог на проекта

Създайте системен потребител, който не може да влиза интерактивно, притежава кода и пуска Gunicorn. Инсталирането на python3-venv и инструменти за компилация избягва провали на pip при пакети, които още компилират 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/

Стъпка 2. Virtualenv и зависимости

Създавайте venv като appuser, за да съвпадат собствениците на файлове. В продукция фиксирайте версии. След инсталация потвърдете, че можете да импортирате приложението с еднократен gunicorn --check-config или Python импорт. Грешките при импорт тук са същите грешки, които после стават 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')"

Стъпка 3. Файл на средата

Не зашивайте SECRET_KEY или URL на база в unit на systemd така, че да се озоват в git, четим за всички. Ползвайте EnvironmentFile. chmod 640, собственик root, група appuser (или собственик appuser, ако предпочитате).

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

Стъпка 4. Услуга systemd за Gunicorn

Вързвайте към 127.0.0.1:8000, не към 0.0.0.0, освен ако имате причина да заобиколите Nginx. Броят работници често е (2 × CPU) + 1 за sync работници; на VPS с 2 vCPU това е 5, което може да е много, ако всеки работник зарежда тежък ML модел — тогава 2–3. За FastAPI задайте --worker-class uvicorn.workers.UvicornWorker и инсталирайте uvicorn. Таймаутите трябва да надхвърлят най-бавната честна заявка, не 30 секунди, ако имате експорти по 2 минути.

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

Стъпка 5. Обратен прокси Nginx и TLS

Nginx слуша на 80/443 и проксира към Gunicorn. client_max_body_size има значение при качвания. proxy_read_timeout трябва да съвпада с таймаута на Gunicorn или да го надхвърля. След като сървърният блок работи по HTTP, издайте сертификат с Certbot (или сменете фронта на Caddy). Фрагментът по-долу е само HTTP, за да тествате; после пуснете Certbot, който може да редактира файла.

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

Стъпка 6. Статични файлове, права и специфики Flask/FastAPI

Nginx трябва да сервира статиката, ако можете; е по-бърз от Gunicorn. Django collectstatic, Flask send_from_directory за мънички приложения или по-късно CDN. appuser трябва да може да чете дървото. Ако ползвате FastAPI, инсталирайте uvicorn[standard] във venv и сменете ExecStart към UvicornWorker. Ако ползвате UNIX сокети вместо TCP, насочете proxy_pass към сокета и напаснете правата, така че www-data да може да пише към него.

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

Работници, памет и презареждане без престой

Sync работниците на Gunicorn са прости и стигат за леки API заявка/отговор. Ако ви трябват много едновременни бавни I/O чакания, помислете за gevent или ASGI работник — мерете, не гадайте. Всеки работник зарежда приложението; RAM ≈ брой работници по RSS на приложението. VPS 2 GB с 8 работника на приложение 300 MB ще отиде в суап и ще се усеща „случайно бавен“. systemctl reload gunicorn (HUP) може да рестартира работниците с новия код, ако сте разгърнали файловете на място; пълният рестарт е по-ясен, когато се сменят зависимости.

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

Отстраняване на 502 и тихи сривове

502 значи, че Nginx не е получил валиден отговор от upstream. Първата команда е journalctl -u gunicorn -e. Чести причини: грешно име module:app, липсващ ключ в .env, Postgres не върви, вързан към 127.0.0.1, а Nginx е на друг хост, SELinux (рядко на Ubuntu) или приложението слуша само на IPv6. 504 е таймаут. Цикли 301 стават, когато приложението пренасочва към HTTP, а X-Forwarded-Proto се игнорира.

  • systemctl status gunicorn — активен ли е?
  • journalctl -u gunicorn -n 100 — ImportError, липсваща env, база
  • curl -v http://127.0.0.1:8000/ от VPS — ако това се провали, Nginx е невинен
  • nginx -t и error.log — upstream prematurely closed connection
  • ss -tulpn | grep 8000 — никой не слуша
  • Дискът е пълен — работниците падат по загадъчни начини

Сигурност

Приложението никога не се връзва публично. Тайните остават в .env. Кърпете venv и ОС. Не пускайте Gunicorn като root. Ако обработвате влизания, задайте бисквитките на сесията Secure и SameSite и настройте рамката да се доверява на X-Forwarded-Proto само от Nginx. Ограничавайте честотата на маршрутите за вход в Nginx или в приложението.

  • bind 127.0.0.1 или unix сокет
  • chmod 600 .env
  • User= не-root в systemd
  • TLS чрез Certbot или Caddy
  • Изключете debug режим и auto-reload в продукция

Съвети

  • Добавете маршрут /health, който проверява свързаността с БД за Compose и балансьори
  • Пращайте статичните активи със заглавка cache-control в Nginx
  • Ползвайте отделен работник или опашка (Redis + systemd) за имейли и тежки задачи
  • Фиксирайте версиите на gunicorn и uvicorn в requirements.txt
  • Направете снимка преди първото продуктово превключване

Python приложение на VPS е готово за продукция, когато върви под Gunicorn като услуга systemd, се връзва само към localhost и се достига през Nginx или Caddy с TLS. Сложете тайните в EnvironmentFile, оразмерете работниците по RAM, а не по формула от блог, и дебъгвайте 502 първо от журнала на Gunicorn. Когато този път е документиран за вашето хранилище, всяко следващо разгръщане е rsync или git pull, pip install и systemctl restart gunicorn.