VDS · Сайты и веб-серверы

Приложение на Python: gunicorn и systemd

Запуск Django, Flask или FastAPI на VDS: виртуальное окружение, gunicorn с сокетом, служба systemd и подбор числа воркеров под ступень.

Обновлено 23 августа 2026 г.

Встроенный сервер разработки (manage.py runserver, flask run) не рассчитан на боевую нагрузку. На VDS ставят gunicorn или uvicorn, а наружу их отдаёт веб-сервер.

Окружение

Никогда не ставьте пакеты проекта в системный Python — обновление системы сломает приложение.

apt install -y python3 python3-venv python3-pip
useradd -r -m -d /srv/app -s /usr/sbin/nologin appuser

sudo -u appuser python3 -m venv /srv/app/venv
sudo -u appuser /srv/app/venv/bin/pip install -r /srv/app/requirements.txt
sudo -u appuser /srv/app/venv/bin/pip install gunicorn

Для FastAPI дополнительно uvicorn[standard] — gunicorn запускает его как класс воркера.

Служба

/etc/systemd/system/myapp.service:

[Unit]
Description=Python app
After=network-online.target postgresql.service

[Service]
User=appuser
WorkingDirectory=/srv/app
EnvironmentFile=/srv/app/.env
ExecStart=/srv/app/venv/bin/gunicorn \
  --workers 3 \
  --bind unix:/run/myapp/gunicorn.sock \
  --access-logfile - \
  myproject.wsgi:application
RuntimeDirectory=myapp
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Для FastAPI замените строку запуска на --worker-class uvicorn.workers.UvicornWorker app.main:app.

systemctl daemon-reload
systemctl enable --now myapp
journalctl -u myapp -f

RuntimeDirectory=myapp создаёт /run/myapp с нужными правами при каждом старте — иначе сокет негде разместить.

Сколько воркеров

Обычная формула — 2 × ядра + 1, но она упирается в память: каждый воркер Django держит 80–200 МБ.

СтупеньЯдраВоркеров
VDS-112
VDS-223
VDS-345
VDS-469

Если приложение много ждёт сеть и базу, добавьте --threads 2 вместо увеличения числа процессов.

Веб-сервер

location / {
    proxy_pass http://unix:/run/myapp/gunicorn.sock;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

location /static/ { alias /srv/app/static/; }

Статику Django отдавайте Nginx напрямую, предварительно собрав её: collectstatic. Пропускать её через Python — лишняя нагрузка.

Частые ошибки

СимптомПричина
502 и connect() to unix:... failed (13)Nginx не имеет прав на сокет — задайте --umask 007 и общую группу
ModuleNotFoundErrorЗапуск системным Python, а не из venv
DisallowedHost в DjangoДомен не добавлен в ALLOWED_HOSTS
Воркеры убиваются по таймаутуДолгий запрос — поднимите --timeout, тяжёлое выносите в очередь
Всё встало после релизаЗабыли systemctl restart myapp — код в памяти не обновляется сам

Секреты держите в EnvironmentFile с правами 600, а не в settings.py. Файл настроек рано или поздно попадает в репозиторий.