Приложение на 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-1 | 1 | 2 |
| VDS-2 | 2 | 3 |
| VDS-3 | 4 | 5 |
| VDS-4 | 6 | 9 |
Если приложение много ждёт сеть и базу, добавьте --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. Файл настроек рано или поздно попадает в репозиторий.