Как запустить Python-приложение на сервере: краткий ответ
Для постоянной работы Flask-, Django- или FastAPI-проекта используйте цепочку: клиент - Nginx - Gunicorn или Uvicorn - Python-приложение. Nginx принимает HTTP и HTTPS, завершает TLS, раздает статику и передает динамические запросы во внутренний процесс. Gunicorn обслуживает WSGI-приложения, Uvicorn - ASGI-приложения. systemd запускает процесс после перезагрузки сервера, пишет журнал и перезапускает сервис при сбое.
Код храните в отдельном каталоге, зависимости устанавливайте в изолированное виртуальное окружение, а секреты передавайте через файл переменных окружения с ограниченными правами. Встроенный сервер Flask, Django или Uvicorn с параметром --reload подходит для локальной проверки команды запуска, но не для публичного production-сервиса.
После настройки у вас будет предсказуемая схема: автозапуск, отдельный пользователь приложения, журналы systemd и Nginx, HTTPS, проверяемый процесс обновления и понятный порядок отката.
Базовая production-схема
| Компонент | Задача | Где искать ошибки |
|---|---|---|
| Flask, Django или FastAPI | Бизнес-логика, API, шаблоны, работа с БД | Логи приложения, traceback, health check |
| Gunicorn | Запуск WSGI-объекта Flask или Django в нескольких worker-процессах | Журнал systemd, параметры bind, импорт модуля |
| Uvicorn | Запуск ASGI-объекта FastAPI и других асинхронных приложений | Журнал systemd, ASGI-точка входа, proxy headers |
| systemd | Автозапуск, управление состоянием процесса, перезапуск после сбоя | systemctl status, journalctl |
| Nginx | Домен, TLS, reverse proxy, статические и медиафайлы | access log, error log, проверка конфигурации |
Типовой маршрут запроса выглядит так: браузер подключается к app.example.test по HTTPS, Nginx принимает запрос на порту 443 и направляет его на 127.0.0.1:8000 или Unix-сокет. Внешний фаервол не должен открывать порт Gunicorn или Uvicorn в интернет. Доступным снаружи оставляют 80 и 443, а SSH ограничивают адресами администраторов.
TLS-сертификат и перенаправление HTTP на HTTPS настраиваются в Nginx. Процесс приложения слушает только localhost или сокет. Такой подход не раскрывает внутренний сервер приложения напрямую и разделяет сетевую конфигурацию с Python-кодом.
Когда достаточно простой схемы, а когда нужна полная
| Сценарий | Допустимый запуск | Что требуется |
|---|---|---|
| Локальная проверка | flask run, manage.py runserver, uvicorn --reload | venv, тестовые переменные, локальный порт |
| Внутренний сервис в закрытой сети | Gunicorn или Uvicorn под systemd | Отдельный пользователь, логи, health check, ограничение сети |
| Публичное приложение | Gunicorn или Uvicorn под systemd за Nginx | venv, HTTPS, домен, firewall, резервные копии, мониторинг |
| Несколько экземпляров или высокий трафик | Несколько процессов за балансировщиком | Повторяемый релиз, наблюдаемость, проверенный rollback |
Для одного сервера связка systemd, Nginx и Gunicorn либо Uvicorn закрывает большую часть задач. Контейнеры, CI/CD и оркестрация нужны, когда команда хочет повторять доставку кода на нескольких узлах, масштабировать сервис или управлять инфраструктурой декларативно. Подходы и критерии выбора разобраны в статье о системах развертывания приложений.
Что проверить до развертывания проекта
До копирования кода зафиксируйте версию Linux, версию Python, способ доступа к базе данных, доменное имя, открытые порты, способ доставки релиза и команду проверки работоспособности. Сверьте эти значения с локальной средой или CI. Установка случайной версии Python либо пакета из репозитория ОС часто приводит к ошибкам импорта уже после запуска службы.
Сервер, пользователь и структура каталогов
Не запускайте веб-процесс от root. Создайте системного пользователя без интерактивного входа, например myapp. Пользователь деплоя может получать код, а пользователь сервиса должен читать исходники, виртуальное окружение, конфигурацию и каталоги, нужные приложению.
sudo useradd --system --create-home --shell /usr/sbin/nologin myapp
sudo install -d -o myapp -g myapp -m 0750 /srv/myapp/releases
sudo install -d -o myapp -g myapp -m 0750 /srv/myapp/shared
sudo install -d -o myapp -g myapp -m 0750 /srv/myapp/shared/static
sudo install -d -o myapp -g myapp -m 0750 /srv/myapp/shared/media
sudo install -d -o root -g myapp -m 0750 /etc/myapp
Для релизов удобно использовать такую структуру:
/srv/myapp/
releases/
20260906-120000/
20260910-154500/
shared/
static/
media/
current -> /srv/myapp/releases/20260906-120000/
/etc/myapp/
myapp.env
Симлинк current указывает на активный релиз. Секреты, пользовательские загрузки и каталоги статики не должны исчезать при удалении старой версии кода. На каждом родительском каталоге нужен бит доступа x для пользователя сервиса, иначе systemd не сможет перейти в WorkingDirectory, даже если права на конечный файл выглядят корректно.
Для размещения приложения подойдет виртуальная машина с доступом по SSH, достаточной памятью и диском под логи, базу данных или кэш. При подборе инфраструктуры можно использовать облачный VPS или VDS, если нужны изменяемые ресурсы, сетевые правила и отдельное хранилище.
Версия Python и системные зависимости
Зафиксируйте минимальную и целевую версию Python в документации проекта, например Python 3.12. Проверьте ее на сервере тем же интерпретатором, который создаст виртуальное окружение:
python3.12 --version
command -v python3.12
python3.12 -m pip --version
Часть Python-пакетов собирает нативные расширения. Для PostgreSQL-пакетов часто нужны заголовки клиентской библиотеки и компилятор, для криптографических пакетов - инструменты сборки и системные библиотеки. Имена пакетов отличаются между Debian, Ubuntu, RHEL, AlmaLinux и другими дистрибутивами. Проверьте документацию используемой ОС, затем повторите установку в чистой тестовой среде.
После установки зависимостей запускайте python -m pip check. Команда находит несовместимые требования уже установленных пакетов. Она не заменяет тесты приложения, но быстро показывает конфликт версий.
Точка входа и команда запуска
До настройки systemd нужно точно знать модуль и объект приложения. Gunicorn и Uvicorn принимают значение в формате модуль:объект. Для Django это обычно myproject.wsgi:application, для FastAPI - myapp.main:app, для Flask - myapp:app либо factory-функция myapp:create_app().
Запишите четыре значения в документации релиза:
- абсолютный путь к каталогу кода;
- путь к интерпретатору или исполняемому файлу Gunicorn/Uvicorn в venv;
- команду запуска и адрес bind;
- health endpoint, например
/healthz, который возвращает HTTP 200 без авторизации.
До публикации через Nginx проверьте внутренний ответ: curl -fsS http://127.0.0.1:8000/healthz. Если запрос не работает напрямую, менять конфигурацию Nginx рано.
Виртуальное окружение и зависимости Python
У каждого приложения должно быть собственное venv. Глобальная установка через sudo pip смешивает зависимости разных проектов и может повредить системные инструменты Linux. systemd не активирует venv так, как это делает интерактивный shell, поэтому в unit-файле всегда указывайте абсолютный путь к Gunicorn или Uvicorn.
Создание и проверка venv
cd /srv/myapp/releases/20260906-120000
python3.12 -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip --version
.venv/bin/python --version
.venv/bin/python -c 'import sys; print(sys.executable)'
Команда python -m pip связывает pip с нужным интерпретатором. Проверка sys.executable должна вывести путь внутри /srv/myapp/releases/.... Если вывод указывает на /usr/bin/python, пакеты устанавливаются не в то окружение.
Активация через source .venv/bin/activate удобна для ручной работы, но не обязательна. Скрипты деплоя и службы надежнее работают с абсолютными путями.
Установка зависимостей воспроизводимым способом
Проект может хранить зависимости в requirements.txt, lock-файле или в связке pyproject.toml и lock-файла менеджера пакетов. Для production используйте зафиксированные версии. Запись fastapi>=0.100 допускает получение другой версии при следующем деплое, а точная версия или lock-файл делают набор пакетов повторяемым.
.venv/bin/python -m pip install --requirement requirements.txt
.venv/bin/python -m pip check
.venv/bin/python -c 'import myapp'
Инструменты разработки, форматтеры, тестовые раннеры и отладочные панели отделяйте от production-зависимостей. Установка полного dev-набора на публичный сервер увеличивает число пакетов и усложняет диагностику уязвимостей.
Если пакет требует доступ к приватному индексу либо репозиторию, передавайте учетные данные через защищенную конфигурацию CI или временный секрет. Не записывайте токен в requirements.txt, Git history или журнал сборки.
Статические файлы, миграции и подготовительные команды
Установка зависимостей, миграции базы данных, сборка статики и запуск веб-процесса решают разные задачи. Не помещайте миграции и collectstatic в ExecStart. При аварийном перезапуске systemd такая команда может запуститься повторно в неподходящий момент.
Подготовительные команды выполняют один раз для каждого релиза и фиксируют их результат:
cd /srv/myapp/releases/20260906-120000
.venv/bin/python manage.py migrate --noinput
.venv/bin/python manage.py collectstatic --noinput
.venv/bin/python manage.py check --deploy
Пример относится к Django. Flask и FastAPI часто используют Alembic, собственную CLI-команду или отдельный миграционный инструмент. Уточните порядок по репозиторию проекта. Миграция должна завершиться до переключения трафика, если новая версия кода требует новую схему базы.
Конфигурация и переменные окружения приложения
Код и конфигурация должны жить раздельно. В репозитории оставляют шаблон .env.example без рабочих паролей. Действующие ключи, строки подключения, токены внешних сервисов и параметры production передают через файл, доступный пользователю службы, или через выделенное хранилище секретов.
Какие значения хранить вне репозитория
SECRET_KEY, ключи подписи cookie и токены API;DATABASE_URL, пароли баз данных и очередей;- SMTP-учетные данные, ключи платежных и внешних API;
- режим окружения, разрешенные хосты, CORS origins;
- пути к постоянному хранилищу, адреса внутренних сервисов и параметры кэша.
Файл /etc/myapp/myapp.env можно подготовить так:
APP_ENV=production
SECRET_KEY=replace-with-secret-from-secure-store
DATABASE_URL=postgresql://myapp:password@127.0.0.1:5432/myapp
ALLOWED_HOSTS=app.example.test
LOG_LEVEL=INFO
sudo chown root:myapp /etc/myapp/myapp.env
sudo chmod 0640 /etc/myapp/myapp.env
Права 0640 дают чтение root и группе приложения. Не выводите содержимое файла командой cat в общий терминал, CI-лог или заявку в службу поддержки. Логи должны маскировать пароли, токены и значения заголовков авторизации.
Различия конфигурации Flask, Django и FastAPI
Flask в production должен работать с DEBUG=False и непустым секретным ключом. Открытый debug-интерфейс дает доступ к подробным ошибкам и может раскрыть внутренние данные. При factory-паттерне проверьте, что factory получает production-конфигурацию до создания приложения.
Django требует корректных ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS для сценариев с формами и отдельной настройки базы, статики и media. Значения схемы и домена должны совпадать с тем, как пользователь открывает сайт через Nginx. Иначе прямой запрос к localhost может проходить, а запрос по HTTPS-домену завершится ошибкой CSRF или host validation.
FastAPI часто читает настройки через Pydantic-конфигурацию или собственный модуль. Явно ограничьте CORS списком нужных origins. Шаблон * не подходит для API с cookie, учетными данными браузера и административными операциями.
Проверка конфигурации без публикации секретов
Добавьте проверку обязательных параметров при старте. Сообщение об ошибке должно называть ключ, но не печатать его значение: DATABASE_URL is missing безопаснее, чем вывод всей строки подключения. Health check после старта может проверить доступ к базе данных, кэшу и критичным зависимостям с ограниченным таймаутом.
Проверяйте соединение от имени пользователя сервиса. Доступ root к сокету базы либо каталогу media не доказывает, что процесс myapp сможет работать с тем же ресурсом.
Как выбрать способ запуска: прямой Python, systemd, Gunicorn или Uvicorn
| Способ | Тип приложения | Автозапуск и перезапуск | Рекомендуемый сценарий |
|---|---|---|---|
| Встроенный сервер фреймворка | Flask, Django, FastAPI | Нет | Локальный smoke-тест |
| Gunicorn | WSGI: Flask, Django | Через systemd | Постоянная работа синхронного веб-приложения |
| Uvicorn | ASGI: FastAPI, Starlette | Через systemd | API, WebSocket, асинхронный ввод-вывод |
| Gunicorn с ASGI worker | ASGI | Через systemd | Если этот режим проверен на версиях проекта |
Nginx не заменяет сервер приложения. Он не импортирует Python-модули, не управляет worker-процессами и не исправляет ошибки зависимостей. Его задача - принять внешний запрос и передать его работающему upstream.
Прямой запуск из виртуального окружения
Прямой запуск нужен, чтобы проверить импорт, точку входа и переменные до создания службы. Выполняйте его в закрытой сети или на localhost.
cd /srv/myapp/current
.venv/bin/python -m flask --app myapp:app run --host 127.0.0.1 --port 8000
.venv/bin/python manage.py runserver 127.0.0.1:8000
.venv/bin/uvicorn myapp.main:app --host 127.0.0.1 --port 8000
Используйте только подходящую строку для своего фреймворка. Не запускайте все три команды. При закрытии SSH-сессии процесс остановится, системный журнал не получит структурированного статуса, а после перезагрузки сервера сервис не поднимется.
Gunicorn для WSGI-приложений
Gunicorn запускает WSGI-объект и создает worker-процессы. Для Django типичная команда указывает WSGI-модуль:
/srv/myapp/current/.venv/bin/gunicorn \
--bind 127.0.0.1:8000 \
--workers 2 \
--timeout 60 \
--access-logfile - \
--error-logfile - \
myproject.wsgi:application
Число workers зависит от числа CPU, объема памяти, длительности запросов и потребления приложения. Начните с 2 workers на небольшом сервере, измерьте потребление памяти и время ответа под нагрузкой, затем скорректируйте значение. Каждый worker импортирует приложение и может открыть собственные соединения с базой, поэтому увеличение числа процессов повышает нагрузку на память и БД.
Параметр --timeout должен превышать нормальную длительность легитимного запроса. Большое значение не исправляет медленный запрос к базе. Сначала найдите причину задержки в трассировке, профилировании или журнале приложения.
Uvicorn и ASGI-приложения
FastAPI и другие ASGI-приложения запускают через Uvicorn. Базовая production-команда выглядит так:
/srv/myapp/current/.venv/bin/uvicorn \
myapp.main:app \
--host 127.0.0.1 \
--port 8000 \
--workers 2
Параметр --reload перезапускает процесс при изменении файлов и нужен разработчику. На сервере он создает лишний наблюдающий процесс и может дать непредсказуемый рестарт во время обновления кода. Не включайте его в systemd unit.
Для ASGI-проекта можно запускать Uvicorn напрямую под systemd или использовать Gunicorn с ASGI worker-классом, если такой режим проверен на используемых версиях. Выберите один путь и зафиксируйте команду в репозитории. Смешение параметров Gunicorn и Uvicorn без проверки затрудняет поиск причин 502 и зависших запросов.
Запуск Flask-приложения через systemd
systemd должен запускать Gunicorn, а не команду flask run. В unit-файле указывают полный путь к исполняемому файлу из venv, рабочий каталог, пользователя и EnvironmentFile. После этого тот же принцип подходит Django и FastAPI, меняется только команда ExecStart.
Настройка systemd для автозапуска Python-приложения
Unit-файл описывает, как Linux запускает и обслуживает процесс. Для WSGI-проекта с Gunicorn создайте /etc/systemd/system/myapp.service. Пример использует localhost-порт, поэтому не требует настройки прав на Unix-сокет на первом запуске.
Ключевые директивы unit-файла
[Unit]
Description=MyApp Gunicorn service
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp/current
EnvironmentFile=/etc/myapp/myapp.env
ExecStart=/srv/myapp/current/.venv/bin/gunicorn --bind 127.0.0.1:8000 --workers 2 --timeout 60 --access-logfile - --error-logfile - myproject.wsgi:application
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
| Директива | Назначение | Частая ошибка |
|---|---|---|
User, Group | Определяют права процесса | Код доступен root, но недоступен пользователю сервиса |
WorkingDirectory | Задает текущий каталог процесса | Относительные импорты, шаблоны или файлы не находятся |
EnvironmentFile | Передает секреты и настройки | Файл отсутствует, имеет неверный формат или не читается |
ExecStart | Запускает сервер приложения | Указан системный Python либо неправильный модуль |
Restart=on-failure | Перезапускает процесс после аварийного завершения | Первичная ошибка скрыта циклом перезапусков |
WantedBy | Подключает автозапуск при загрузке ОС | Сервис работает вручную, но не стартует после reboot |
Сервис FastAPI использует иной ExecStart:
ExecStart=/srv/myapp/current/.venv/bin/uvicorn myapp.main:app --host 127.0.0.1 --port 8000 --workers 2
Права, рабочий каталог и доступ к файлам
Интерактивный shell передает PATH, HOME, текущий каталог и активированное venv. systemd не повторяет это окружение. Абсолютные пути в ExecStart, WorkingDirectory и EnvironmentFile устраняют значительную часть ошибок запуска.
Проверьте, что пользователь myapp читает код и venv, записывает в нужные каталоги media или временных файлов, а Nginx получает доступ к статике. Не давайте веб-процессу права записи на каталог релиза. Исходный код релиза после публикации лучше считать неизменяемым.
При выборе Unix-сокета добавьте каталог времени выполнения, назначьте группу, доступную Nginx, и проверьте маску сокета. Для первой установки localhost-порт проще диагностировать командой ss -ltnp.
Статус, журналы и автоматический перезапуск
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
sudo systemctl status myapp.service
sudo journalctl -u myapp.service -b -n 100 --no-pager
curl -fsS http://127.0.0.1:8000/healthz
После изменения unit-файла всегда выполняйте systemctl daemon-reload. После изменения кода или зависимостей перезапустите сервис. Команда daemon-reload читает только конфигурацию systemd, она не перезагружает Python-код.
Цикл перезапусков означает, что процесс завершается с ошибкой. Остановите сервис, прочитайте первое полезное исключение в журнале и повторите команду ExecStart вручную от имени пользователя приложения. Не пытайтесь лечить ImportError уменьшением RestartSec или переустановкой всего сервера.
Nginx как reverse proxy: домен, HTTPS и статика
Nginx принимает внешний HTTP-трафик, передает заголовок исходного хоста и IP-адрес приложению, ограничивает размер тела запроса и раздает файлы без участия Python-worker. Это снижает нагрузку на Gunicorn или Uvicorn и упрощает работу с TLS.
Проксирование на порт или Unix-сокет
Проксирование на 127.0.0.1:8000 проще при первом развертывании: порт видно через ss, а права файлов не влияют на подключение. Unix-сокет убирает TCP-порт из локальной схемы, но требует аккуратной настройки владельца, группы и прав.
server {
listen 80;
server_name app.example.test;
client_max_body_size 20m;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
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_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
location /static/ {
alias /srv/myapp/shared/static/;
}
}
Для Unix-сокета строка proxy_pass имеет вид proxy_pass http://unix:/run/myapp/gunicorn.sock:;. Путь в Nginx должен совпадать с параметром --bind Gunicorn. Ошибка в одном символе приводит к 502 Bad Gateway.
Заголовки, таймауты и большие запросы
Заголовки Host, X-Forwarded-For и X-Forwarded-Proto передают приложению исходный домен, IP клиента и внешнюю HTTPS-схему. Без X-Forwarded-Proto приложение может строить HTTP-ссылки, неверно выставлять secure cookie или бесконечно перенаправлять запросы.
client_max_body_size 20m допускает загрузку тела до 20 МБ. Выберите значение по реальному размеру файлов и ограничениям приложения. Ошибка 413 означает, что лимит Nginx меньше передаваемого тела запроса.
Таймауты должны соответствовать поведению API. Обычная страница может требовать 10-30 секунд, импорт файла или отчет - больше. При WebSocket добавьте отдельную конфигурацию upgrade-заголовков и увеличьте таймаут только для нужного location. Не распространяйте завышенные значения на все запросы.
HTTPS и статические файлы
Сначала проверьте DNS и виртуальный хост HTTP, затем добавьте TLS-конфигурацию, сертификат и перенаправление с 80 на 443. После каждого изменения выполняйте sudo nginx -t, затем перечитывайте конфигурацию Nginx. Проверьте автоматическое продление сертификата до передачи сервиса в эксплуатацию.
server {
listen 80;
server_name app.example.test;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name app.example.test;
ssl_certificate /path/to/certificate.pem;
ssl_certificate_key /path/to/private-key.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Django после collectstatic обычно передает собранные файлы в каталог, который раздает Nginx через alias. Flask может хранить static внутри пакета, но для крупных файлов лучше выделить постоянный каталог. FastAPI подключает статические ресурсы только при необходимости, а Nginx все равно способен раздавать их напрямую.
Полный порядок DNS, Nginx, HTTPS, проверки API и безопасного релиза описан в материале о production-деплое веб-приложения. Для повторяемой настройки Nginx и нагрузочных проверок пригодятся Ansible-плейбуки и тесты Nginx.
Запуск Django-проекта на сервере и особенности Flask и FastAPI
Фреймворки используют одну инфраструктурную схему, но отличаются точкой входа, подготовительными командами и настройками production. Выполняйте команды каждого сценария отдельно. Команда, предназначенная для Django, не поможет диагностировать FastAPI, и наоборот.
Flask: модуль и объект WSGI
Если файл myapp.py содержит объект app, Gunicorn запускают так:
/srv/myapp/current/.venv/bin/gunicorn --bind 127.0.0.1:8000 myapp:app
При factory-паттерне объект создается функцией. Формат команды меняется:
/srv/myapp/current/.venv/bin/gunicorn --bind 127.0.0.1:8000 'myapp:create_app()'
Проверьте импорт в каталоге релиза: .venv/bin/python -c 'from myapp import app'. Если приложение создается factory-функцией, импортируйте функцию, не вызывая тяжелые внешние операции в модуле без необходимости. Ошибка соединения с базой при импорте может не дать service manager даже создать worker.
Отключите debug, задайте SECRET_KEY, убедитесь в доступности шаблонов и статических файлов. Не публикуйте Werkzeug debugger через Nginx.
Django: настройки, миграции и статика
Проверьте, что manage.py использует production-настройки. Часто это задается переменной:
DJANGO_SETTINGS_MODULE=myproject.settings.production
Перед запуском Gunicorn выполните проверки и подготовительные команды:
cd /srv/myapp/current
.venv/bin/python manage.py check --deploy
.venv/bin/python manage.py migrate --noinput
.venv/bin/python manage.py collectstatic --noinput
.venv/bin/gunicorn --bind 127.0.0.1:8000 myproject.wsgi:application
В production-настройках укажите домен в ALLOWED_HOSTS, настройте подключение к базе, каталоги STATIC_ROOT и MEDIA_ROOT, а для HTTPS-сценариев проверьте trusted origins и secure cookie. Успешный старт Gunicorn не доказывает готовность Django: отдельный запрос должен проверить базу, миграции, статику и авторизацию.
FastAPI: ASGI, Uvicorn и фоновые задачи
FastAPI передает Uvicorn ASGI-объект, например:
cd /srv/myapp/current
.venv/bin/uvicorn myapp.main:app --host 127.0.0.1 --port 8000 --workers 2
Когда приложение стоит за Nginx, разрешайте доверие к proxy headers только для адреса внутреннего прокси. Это нужно для корректной HTTPS-схемы и клиентских адресов, но доверие к заголовкам от произвольного клиента позволяет подменять сведения о запросе. CORS ограничьте перечнем фронтенд-доменов.
Критичные и длительные фоновые задачи не храните только внутри веб-worker. Перезапуск службы, масштабирование workers и сбой процесса прерывают такую работу. Для очереди, отправки писем, обработки файлов или периодических заданий подготовьте отдельный worker, устойчивое хранилище задач и наблюдение за результатом.
Безопасное обновление Python-приложения без простоя
Обновление не должно перезаписывать работающий код и конфигурацию. Готовьте новую версию в отдельном каталоге, устанавливайте зависимости, выполняйте проверки, затем переключайте активный релиз. Старый каталог храните до окончания наблюдения за новой версией.
Релизы, общая конфигурация и симлинк current
Каталог releases содержит неизменяемые версии кода и их venv. В shared лежат загружаемые файлы и общая статика. Файл /etc/myapp/myapp.env остается вне всех релизов. Unit-файл с путем /srv/myapp/current/.venv/bin/gunicorn после рестарта использует новый релиз.
cd /srv/myapp
ln -s /srv/myapp/releases/20260910-154500 current.new
mv -Tf current.new current
sudo systemctl restart myapp.service
Переименование ссылки выполняется атомарно на одной файловой системе. Сначала убедитесь, что новый каталог полностью подготовлен. Переключение пустой или частично скопированной версии создает отказ независимо от атомарности симлинка.
Reload, graceful restart и проверка работоспособности
systemctl daemon-reload читает изменения unit-файла. systemctl restart завершает текущий процесс и запускает новый. При одном экземпляре приложения рестарт может кратко прервать запросы. Не обещайте нулевую недоступность без теста поведения именно вашего процесса и его соединений.
Gunicorn поддерживает управляемую замену worker-процессов, но применимость сигнала и порядок перезагрузки зависят от команды запуска, открытых соединений, состояния приложения и версии сервера. Проверьте процедуру на тестовом узле с активными запросами. Для гарантированного переключения без разрыва трафика нужны два экземпляра приложения, например blue и green, отдельные локальные порты или сокеты, health check и переключение upstream Nginx после проверки нового экземпляра.
После релиза проверьте минимум четыре вещи: внутренний health endpoint, внешний HTTPS endpoint, журнал приложения и ключевой пользовательский сценарий. Сохраняйте время начала релиза, версию кода и результат проверки, чтобы быстро сопоставить ошибку с изменением.
Миграции базы данных и откат
Код можно вернуть на предыдущий релиз за секунды, а схему базы не всегда. Перед миграциями создайте резервную копию и проверьте восстановление отдельно. Новые миграции по возможности должны сохранять совместимость с предыдущей версией приложения: сначала добавить поле или таблицу, затем перевести код на новое поле, удалить старую структуру в следующем релизе.
Порядок безопасного релиза:
- Создайте резервную копию базы данных и критичных media-файлов.
- Разместите код в новом каталоге
releases. - Создайте venv и установите зафиксированные зависимости.
- Выполните миграции, совместимые с предыдущей версией.
- Соберите статику и выполните локальный smoke-тест.
- Переключите
current, перезапустите сервис и проверьте внешний endpoint. - При сбое верните симлинк на предыдущий релиз и выполните проверенный rollback базы, если он нужен.
Возврат ссылки не отменяет разрушительную миграцию. План отката должен отдельно описывать каждое изменение схемы, которое невозможно безопасно откатить автоматически.
Типовые ошибки при деплое и алгоритм диагностики
Диагностику начинайте с разделения уровней: DNS и сеть, Nginx, systemd, сервер приложения, импорт Python, конфигурация, база данных и код. Проверяйте их в этом порядке. Сначала получите первый полезный traceback, затем исправляйте причину. Перезапуск процесса без чтения исключения обычно только стирает контекст.
| Симптом | Где смотреть | Вероятная причина | Первое действие |
|---|---|---|---|
| Сервис постоянно перезапускается | journalctl -u myapp.service | Ошибка импорта, неверный путь, отсутствует переменная | Проверить первое исключение и ExecStart |
| 502 Bad Gateway | Nginx error log, systemctl status | Upstream не слушает порт или сокет недоступен | Проверить внутренний запрос и bind |
| 504 Gateway Timeout | Nginx log, журнал приложения, БД | Долгий запрос, зависший worker, малый таймаут | Измерить длительность и найти медленную операцию |
| 403 на статике | Nginx error log | Нет прав прохода по каталогу или чтения файлов | Проверить владельца и биты доступа |
| ModuleNotFoundError | Журнал systemd | Системный Python вместо venv, неверный модуль | Сверить абсолютный путь и импорт вручную |
Сервис не запускается или уходит в цикл перезапусков
sudo systemctl status myapp.service
sudo journalctl -u myapp.service -b -n 100 --no-pager
sudo systemctl stop myapp.service
sudo -u myapp /srv/myapp/current/.venv/bin/python -c 'import myproject'
sudo ss -ltnp
Проверьте ExecStart, WorkingDirectory, переменные окружения, доступ к каталогу, импорт модуля, занятый порт и системные зависимости. Команда, работающая от root, может завершаться ошибкой от имени myapp из-за прав, HOME или отсутствия конфигурации.
Если журнал содержит несколько traceback, начинайте с первого по времени. Последующие ошибки часто возникают уже из-за неуспешного старта зависимого компонента.
Nginx возвращает 502, 504 или 403
Код 502 означает, что Nginx не получил корректный ответ от upstream. Проверьте статус службы, адрес --bind, внутренний curl-запрос и права на Unix-сокет. Для localhost-порта сравните значение в proxy_pass и ExecStart.
Код 504 означает, что upstream не ответил в установленный срок. Сверьте время запроса в access log с proxy_read_timeout, посмотрите загрузку CPU, память, журнал базы и число свободных workers. Рост таймаута допустим для известной долгой операции, но не заменяет исправление блокировки или неэффективного запроса.
Код 403 при раздаче статики обычно связан с правами Nginx на каталог или неверным alias. Проверьте, что путь в конфигурации существует, заканчивается слешем при использовании alias и доступен пользователю веб-сервера.
Ошибки импорта и несовместимые зависимости
ImportError, ModuleNotFoundError и ошибки нативных библиотек требуют проверки окружения, а не случайной смены параметров Nginx. Сначала посмотрите полную трассировку, затем проверьте путь интерпретатора, список пакетов, версию Python и системные библиотеки.
/srv/myapp/current/.venv/bin/python --version
/srv/myapp/current/.venv/bin/python -m pip list
/srv/myapp/current/.venv/bin/python -m pip check
cd /srv/myapp/current
/srv/myapp/current/.venv/bin/python -c 'import myapp'
Linux чувствителен к регистру имен файлов. Импорт from MyApp import app может работать на одной рабочей станции и не работать на сервере, если фактическое имя каталога myapp. Проверьте регистр, структуру пакетов и наличие __init__.py там, где его ожидает проект.
Flask: debug, factory и переменные окружения
Для Flask проверьте формат точки входа: myapp:app подходит для готового WSGI-объекта, а myapp:create_app() - для factory-функции. Ошибка Failed to find attribute указывает на несоответствие модуля и объекта.
Проверьте DEBUG=False, SECRET_KEY, конфигурацию базы и путь к шаблонам. Не храните зависимость от текущего каталога без явного WorkingDirectory. Flask-приложение, запущенное через development server, не должно принимать публичные запросы.
Django: ALLOWED_HOSTS, миграции и статика
Ошибка DisallowedHost означает, что домен отсутствует в ALLOWED_HOSTS. Добавьте точные доменные имена, используемые Nginx. Для HTTPS-форм проверьте CSRF_TRUSTED_ORIGINS и схему адреса.
Ошибки таблиц базы после релиза проверяют командами manage.py showmigrations и manage.py migrate. Ошибки статики ищите в результате collectstatic, настройках STATIC_ROOT и правах Nginx на целевой каталог. Разделяйте сбой запуска процесса и сбой конкретного HTTP-запроса: это разные уровни диагностики.
FastAPI: ASGI, прокси-заголовки и CORS
FastAPI требует ASGI-точку входа. Попытка запустить myapp.main:app через WSGI-схему без ASGI worker приводит к ошибке интерфейса или некорректной обработке запросов. Сначала проверьте Uvicorn напрямую на localhost, затем Nginx.
Если внутренний curl-запрос работает, а браузерный запрос через домен получает ошибку схемы, редиректа или cookie, проверьте X-Forwarded-Proto в Nginx и настройку доверия к proxy headers в Uvicorn. CORS-ошибка в браузере требует точной проверки origin, метода, заголовков и поддержки credential-сценария. Она не исправляется открытием API-порта наружу.
Безопасность и контроль работы после деплоя
Работающий процесс еще не готов к постоянной эксплуатации. Проверьте права, сетевые правила, журналы, ресурсные лимиты, резервные копии и сценарий восстановления. Эти проверки сокращают время поиска неисправности и уменьшают последствия ошибки в приложении или учетной записи.
Минимальные права и защита секретов
- Запускайте приложение под отдельным системным пользователем без shell-входа.
- Храните секреты вне Git и ограничивайте доступ к
EnvironmentFile. - Оставляйте порт Gunicorn или Uvicorn доступным только через localhost либо Unix-сокет.
- Разрешайте через фаервол только нужные входящие порты: SSH для администраторов, HTTP и HTTPS для публичного сервиса.
- Используйте SSH-ключи, проверьте аварийный доступ до отключения парольной аутентификации.
- Отключите debug-режим и подробный вывод исключений пользователю.
Директивы systemd NoNewPrivileges=true и PrivateTmp=true дают базовую изоляцию. Более строгие ограничения, например защита файловой системы и ограничение путей записи, добавляйте после теста: приложение может требовать запись в media, кэш, временные файлы или Unix-сокет.
Логи, мониторинг и контроль ресурсов
| Что контролировать | Где смотреть | Что показывает проблема |
|---|---|---|
| Статус процесса | systemctl status myapp.service | Падения, перезапуски, код завершения |
| Журнал приложения | journalctl -u myapp.service | Traceback, ошибки конфигурации, ошибки БД |
| Запросы Nginx | access log и error log | Коды 4xx и 5xx, таймауты, ошибки upstream |
| CPU и память | Системный мониторинг | Утечки, нехватка workers, OOM-завершения |
| Диск | Системный мониторинг | Переполнение логами, файлами, резервными копиями |
| Сертификат | Проверка TLS и задачи продления | Риск истечения срока и недоступности HTTPS |
Настройте ротацию логов или убедитесь, что журнал systemd и Nginx не заполняют диск. Отслеживайте время ответа health endpoint, число 5xx-ответов, свободное место, память и срок TLS-сертификата. Набор инструментов зависит от существующей инфраструктуры, но метрики и ответственные за реакцию должны быть определены заранее.
Финальный чек-лист перед передачей сервиса в работу
- Зафиксированы версии Linux, Python, зависимостей, Gunicorn или Uvicorn и Nginx.
- Код работает из venv от имени пользователя приложения.
- systemd-сервис включен и корректно стартует после перезагрузки сервера.
- Внешний запрос по домену использует HTTPS, а HTTP перенаправляется.
- Встроенный порт приложения не доступен из интернета.
- Health check возвращает ожидаемый код ответа через localhost и через домен.
- Проверены авторизация, статика, загрузка файлов и критичный пользовательский сценарий.
- Секреты отсутствуют в Git, логах и каталогах релизов.
- Есть резервная копия базы и файлов, а восстановление было проверено.
- Описан rollback кода и отдельно оценен rollback миграций базы данных.
После приемки сохраните unit-файл, Nginx-конфигурацию, список переменных без секретных значений, команды релиза и процедуру восстановления в документации проекта. Это позволяет повторить развертывание на новом сервере и быстрее восстановить сервис после сбоя.