Як запускаць Python-праграмы праз Gunicorn і Nginx на VPS
Выкладка Flask або FastAPI на Ubuntu: віртуальнае асяроддзе, служба systemd для Gunicorn, зваротны проксі Nginx, TLS, файл зменных, часопісы і разбор 502 і ліку воркераў.

Убудаваны сервер Flask і uvicorn --reload — для распрацоўкі. На публічным VPS патрэбны менеджар працэсаў, які перазапускае воркеры, слухае localhost і стаіць за зваротным проксі з TLS і павольнымі кліентамі. Gunicorn — звычайны WSGI-выбар для Flask і Django. FastAPI можна ганяць пад Gunicorn з класам воркера Uvicorn. Nginx (або Caddy) завяршае HTTPS і перасылае на 127.0.0.1:8000.
Ніжэй схема, якая перажывае перазагрузку: праект у /srv/app, віртуальнае асяроддзе, .env з сакрэтамі, unit gunicorn.service, віртуальны хост Nginx, Let's Encrypt і часопісы, якія можна grep’аць. Разбяром лік воркераў, таймаўты і чаму 502 Bad Gateway амаль ніколі не значыць «зламаўся Nginx»: звычайна Gunicorn не запушчаны, слухае не той сокет або падае на імпарце. Прыклад на Flask, для FastAPI — асобныя заўвагі па камандзе.
Навошта гэты стэк
Gunicorn загадзя паднімае працэсы-воркеры. Кожны sync-воркер у адзін момант апрацоўвае адзін запыт, калі не змяніць клас воркера. Nginx буферызуе павольных кліентаў, каб воркеры не захрасалі на адпраўцы ў мабільную сетку. systemd паднімае праграму пасля падзення. Разам гэта нудна — а апоўначы менавіта гэта патрэбна.
- Gunicorn: прадказальная мадэль WSGI/ASGI
- systemd: старт пры загрузцы, перазапуск пры падзенні, часопісы journald
- Nginx: TLS, статыка, ліміт памеру запыту, gzip
- venv: сістэмны Python не засмечваецца
- Прывязка да 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.
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. Віртуальнае асяроддзе і залежнасці
Стварайце venv ад appuser, каб уладальнікі файлаў супалі. У баі версіі пакетаў фіксуйце. Пасля ўсталёўкі пераканайцеся, што праграма імпартуецца — тыя самыя памылкі потым ператворацца ў 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')"Крок 3. Файл зменных асяроддзя
Не зашывайце SECRET_KEY і URL базы ў unit так, каб гэта выцекла ў git з правамі на чытанне ўсім. Выкарыстоўвайце EnvironmentFile. chmod 640, уладальнік root, група appuser (або ўладальнік appuser, калі так зручней).
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. Лік sync-воркераў часта (2 × CPU) + 1; на 2 vCPU гэта 5, і гэтага можа быць шмат, калі кожны воркер грузіць цяжкую мадэль — тады 2–3. Для FastAPI укажыце --worker-class uvicorn.workers.UvicornWorker і пастаўце uvicorn. Таймаўт павінен быць большы за найдаўжэйшы сумленны запыт, а не 30 секунд, калі ў вас выгрузкі па дзве хвіліны.
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 потым можа яго правіць.
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 мог пісаць.
# 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Воркеры, памяць і перазагрузка без лішняй драмы
Сінхронныя воркеры Gunicorn простыя і падыходзяць лёгкім API запыт-адказ. Шмат адначасовых павольных I/O — глядзіце gevent або ASGI, але вымярайце, не гадайце. Кожны воркер загружае праграму; RAM ≈ лік воркераў × RSS праграмы. VPS на 2 ГБ і 8 воркераў па 300 МБ пойдуць у падкачку і будуць «часам тармазіць». systemctl reload gunicorn (HUP) перазапускае воркеры з новым кодам, калі файлы ўжо на месцы; поўны restart зразумелейшы, калі змяніліся залежнасці.
# 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 — служба active?
- journalctl -u gunicorn -n 100 — ImportError, няма зменнай, база
- 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. Калі ёсць уваход карыстальнікаў, cookie сесіі Secure і SameSite, фрэймворк давярае X-Forwarded-Proto толькі ад Nginx. Ліміт частаты на логін — у Nginx або ў праграме.
- bind 127.0.0.1 або UNIX-сокет
- chmod 600 у .env
- User= не root у systemd
- TLS праз Certbot або Caddy
- debug і аўтаперазагрузка ў баі выключаныя
Парады
- Маршрут /health з праверкай базы для Compose і балансіроўшчыкаў
- Для статыкі ў Nginx задайце Cache-Control
- Лісты і цяжкія задачы — асобны воркер або чарга (Redis + systemd)
- Версіі gunicorn і uvicorn фіксуйце ў requirements.txt
- Здымак дыска перад першым баявым пераключэннем DNS
Python-праграма на VPS гатовая да бою, калі яна круціцца пад Gunicorn як служба systemd, слухае толькі localhost і даступная праз Nginx або Caddy з TLS. Сакрэты — у EnvironmentFile, лік воркераў — па RAM, а не па чужой формуле з блога, 502 спачатку глядзіце ў часопісе Gunicorn. Калі гэты шлях запісаны для рэпазіторыя, кожная наступная выкладка — rsync або git pull, pip install і systemctl restart gunicorn.