TürkiyeVPS

Запуск Python-приложения на VPS: venv, systemd и обратный прокси Nginx

Пошаговое руководство по постоянному запуску веб-приложения или бота на Python на VPS: виртуальное окружение (venv), Gunicorn, служба systemd, обратный прокси Nginx и бесплатный SSL.

7 мин чтения

Когда приложение, которое у вас на компьютере без проблем запускается командой python app.py, переезжает на сервер, возникают новые вопросы: почему приложение останавливается, когда закрываешь терминал? Что будет после перезагрузки сервера? Как открыть приложение по доменному имени и через HTTPS? В этом руководстве мы опубликуем веб-приложение на Python 3.12 на VPS с Ubuntu 24.04 так, как это принято в продакшене.

Мы построим такую схему:

  1. Приложение работает в собственном виртуальном окружении (venv).
  2. Gunicorn запускает приложение на порту, доступном только изнутри сервера.
  3. systemd управляет Gunicorn как службой: запускает при загрузке и перезапускает при сбое.
  4. Nginx принимает входящие запросы извне и передаёт их приложению (обратный прокси, reverse proxy).

В примере используется Flask. Для Django или FastAPI шаги почти те же; отличия мы отметим по ходу.

1. Подготовьте сервер

Подключитесь по SSH, обновите пакеты и установите нужные инструменты:

sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-venv python3-pip nginx
python3 --version

В Ubuntu 24.04 команда python3 --version должна вывести Python 3.12.x. С точки зрения безопасности важно запускать приложение не от root, а от отдельного пользователя с ограниченными правами:

sudo adduser --system --group --home /srv/myapp myapp

2. Разместите приложение и создайте виртуальное окружение

Поместите код в папку /srv/myapp/app. Если вы используете Git:

sudo -u myapp git clone https://github.com/kullanici/proje.git /srv/myapp/app

Для проверки можно написать и небольшое приложение на Flask. /srv/myapp/app/app.py:

from flask import Flask

app = Flask(__name__)

@app.get("/")
def index():
    return "Merhaba, uygulama VPS'te çalışıyor!"

Теперь создайте виртуальное окружение и установите зависимости:

cd /srv/myapp
sudo -u myapp python3 -m venv venv
sudo -u myapp venv/bin/pip install --upgrade pip
sudo -u myapp venv/bin/pip install flask gunicorn

Если в проекте есть requirements.txt, вместо последней строки используйте sudo -u myapp venv/bin/pip install -r app/requirements.txt gunicorn.

Зачем venv? Ubuntu 24.04 не даёт устанавливать пакеты через pip в системный Python (ошибка externally-managed-environment). Это сознательная защита: она не позволяет сломать пакеты, которыми пользуются системные инструменты. Отдельное venv для каждого проекта к тому же устраняет конфликты зависимостей.

3. Проверьте с Gunicorn

Встроенный сервер разработки Flask не подходит для продакшена. Вместо него мы будем использовать Gunicorn:

cd /srv/myapp/app
sudo -u myapp ../venv/bin/gunicorn --bind 127.0.0.1:8000 app:app

Выполнив curl http://127.0.0.1:8000 из другого терминала, вы должны увидеть приветственный текст. app:app означает «объект app в файле app.py». В Django это обычно proje.wsgi:application. Для ASGI-приложений вроде FastAPI к Gunicorn добавляется параметр -k uvicorn.workers.UvicornWorker (для этого нужно установить и пакет uvicorn).

После проверки остановите сервер сочетанием Ctrl+C. Обратите внимание, что приложение привязано к 127.0.0.1: снаружи к нему напрямую не подключиться, трафик будет приходить через Nginx.

4. Создайте службу systemd

Чтобы приложение не останавливалось при закрытии терминала и само запускалось после перезагрузки сервера, напишем службу systemd:

sudo nano /etc/systemd/system/myapp.service

Содержимое:

[Unit]
Description=myapp Gunicorn servisi
After=network.target

[Service]
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp/app
Environment="PATH=/srv/myapp/venv/bin"
EnvironmentFile=-/srv/myapp/.env
ExecStart=/srv/myapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 app:app
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Несколько пояснений:

  • EnvironmentFile позволяет хранить секреты, например пароль базы данных или API-ключ, отдельно от кода. Знак - в начале нужен, чтобы служба запускалась, даже если файла нет.
  • --workers 3 — разумное начало для небольшого сервера. Распространённая рекомендация — (2 × число ядер) + 1, но учитывайте и объём RAM.
  • Restart=on-failure перезапускает приложение через пять секунд, если оно завершилось с ошибкой.

Включите и запустите службу:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp

В строке состояния должно быть active (running). Если есть ошибка, загляните в журналы:

journalctl -u myapp -n 50 --no-pager

5. Настройте Nginx как обратный прокси

Сначала направьте A-запись DNS вашего домена на IP-адрес VPS. Затем создайте файл сайта для Nginx:

sudo nano /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name ornek.com www.ornek.com;

    client_max_body_size 20M;

    location /static/ {
        alias /srv/myapp/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;
    }
}

Блок /static/ позволяет Nginx отдавать CSS, JS и изображения напрямую, не нагружая Python. Если такой папки в приложении нет, блок можно удалить. Включите сайт:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Команда nginx -t проверяет файл настроек на ошибки; возьмите за привычку запускать её после каждого изменения.

6. Файрвол и HTTPS

Если вы используете UFW, разрешите SSH и веб-трафик:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

Чтобы получить бесплатный SSL-сертификат от Let's Encrypt:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d ornek.com -d www.ornek.com

Certbot сам добавит HTTPS-блок в конфигурацию Nginx и настроит автоматическое продление сертификата. Проверить, что продление работает, можно командой sudo certbot renew --dry-run.

Обновление приложения

Что нужно сделать после изменений в коде:

cd /srv/myapp/app
sudo -u myapp git pull
sudo -u myapp ../venv/bin/pip install -r requirements.txt
sudo systemctl restart myapp

Храните секреты отдельно от кода

Если записать в код такие значения, как пароль базы данных, API-ключ или SECRET_KEY, они могут утечь, когда код попадёт в Git-репозиторий. Для этого используйте файл /srv/myapp/.env, который мы указали в службе systemd:

sudo nano /srv/myapp/.env
DATABASE_URL=postgresql://myapp:[email protected]:5432/myapp
SECRET_KEY=uzun-ve-rastgele-bir-deger

Ограничьте права на файл так, чтобы его мог читать только пользователь приложения, и перезапустите службу:

sudo chown myapp:myapp /srv/myapp/.env
sudo chmod 600 /srv/myapp/.env
sudo systemctl restart myapp

В Python эти значения читаются через os.environ["DATABASE_URL"]. Чтобы сгенерировать случайный надёжный ключ, можно использовать команду python3 -c "import secrets; print(secrets.token_hex(32))".

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

  • Запуск приложения от root: если в приложении найдётся уязвимость, злоумышленник захватит весь сервер. Всегда используйте отдельного пользователя с ограниченными правами.
  • Открытие сервера разработки наружу: flask run --host 0.0.0.0 или команда Django runserver не предназначены для продакшена. Используйте Gunicorn и Nginx.
  • Включённый режим отладки: debug=True во Flask и DEBUG = True в Django могут показывать ваш код и настройки на страницах ошибок.
  • Забытая настройка ALLOWED_HOSTS в Django: если не добавить домен в этот список, запросы через Nginx получат ошибку «Bad Request (400)». Не забудьте также выполнить collectstatic для статических файлов.
  • Права на папки: чтобы Nginx мог читать статические файлы, папка /srv/myapp/app/static и родительские папки должны быть доступны для чтения пользователю www-data. Если вы получаете ошибку 403, начните проверку отсюда.

Если вы запускаете бота или фоновую задачу

Для бота Telegram/Discord или скрипта без веб-интерфейса Nginx не нужен. Достаточно в том же файле systemd заменить строку ExecStart на /srv/myapp/venv/bin/python bot.py. systemd будет держать бота запущенным и перезапускать его при сбоях.

Какого сервера достаточно?

Для небольшого приложения на Flask/FastAPI или бота чаще всего хватает VPS с 1–2 vCPU и 1–2 ГБ RAM. Если у вас модель машинного обучения, тяжёлые фоновые задачи или постоянно высокая загрузка процессора, стабильнее будет сервер, ресурсы которого не делятся с другими. Пакеты VPS и VDS TürkiyeVPS предоставляются с root-доступом — каждый шаг этого руководства можно выполнить на собственном сервере.

Часто задаваемые вопросы

Разве не хватит запуска через screen или tmux?

Для проверки хватит, но после перезагрузки сервера или падения приложения оно само не поднимется. systemd обеспечивает и автозапуск, и ведение журналов, поэтому для постоянно работающих приложений предпочтителен именно он.

Я получаю ошибку «externally-managed-environment». Что делать?

Эта ошибка появляется, когда вы пытаетесь выполнить pip install прямо в системный Python. Создайте для проекта виртуальное окружение командой python3 -m venv venv и устанавливайте пакеты через venv/bin/pip.

Приложение выдаёт ошибку 502 Bad Gateway. Почему?

Это значит, что Nginx не может достучаться до приложения за ним. Проверьте с помощью systemctl status myapp, что служба работает, через journalctl -u myapp — нет ли ошибок, и убедитесь, что порт в proxy_pass в Nginx совпадает с портом Gunicorn.

Можно ли запускать несколько Python-приложений на одном VPS?

Да. Для каждого приложения достаточно создать отдельное venv, использовать свой порт (например, 8000, 8001), отдельную службу systemd и отдельный блок server в Nginx.