⚡
Хостинг

Как развернуть FastAPI на сервере: Uvicorn, systemd и Nginx

08.11.2026
← Все статьи

FastAPI создан для того, чтобы быстро писать и быстро работать, поэтому его всё чаще выбирают для API мобильных приложений, Telegram-ботов и внутренних сервисов. Локально всё запускается за секунду командой uvicorn main:app --reload. Но этот режим предназначен для разработки: --reload следит за файлами, работает в одном процессе и не восстанавливается сам после перезагрузки сервера. Разберём, как правильно разместить FastAPI на VPS, превратить его в постоянно работающий сервис и открыть через Nginx с HTTPS.

Что такое ASGI и зачем нужен Uvicorn

FastAPI основан на стандарте ASGI, то есть поддерживает асинхронные запросы, WebSocket-соединения и долгоживущие подключения. Чтобы запустить такое приложение, нужен ASGI-сервер, и самый распространённый из них — Uvicorn. Он может запускать несколько рабочих процессов, а каждый процесс параллельно обрабатывает множество асинхронных запросов. Nginx стоит впереди и берёт на себя HTTPS-шифрование, медленных клиентов и ограничения на размер запросов, так что Python-процессы заняты только логикой приложения.

Пример: ташкентская чайхана с национальной кухней заказала мобильное приложение для доставки. Приложение обращается к сервису FastAPI по адресу api.misol.uz: получает меню, отправляет заказы и через WebSocket следит за местоположением курьера. В обеденное время число запросов резко растёт, поэтому сервис должен работать с несколькими воркерами, автоматически восстанавливаться и стоять за правильно настроенным прокси.

Подготовка проекта на сервере

Создайте для приложения отдельного системного пользователя и виртуальное окружение. Зависимости ставьте из requirements.txt; вариант Uvicorn standard подтягивает более быстрый цикл событий и библиотеки для WebSocket.

sudo apt update
sudo apt install python3-venv nginx
sudo adduser --system --group --home /srv/api api
sudo -u api python3 -m venv /srv/api/venv
sudo -u api /srv/api/venv/bin/pip install -r /srv/api/requirements.txt "uvicorn[standard]"

Пароль к базе, ключ JWT и токены внешних сервисов хранятся не в коде, а в файле /srv/api/.env, и приложение читает их как переменные окружения. Поставьте на файл chmod 600, чтобы доступ к нему был только у пользователя api. Сначала запустите приложение вручную и проверьте, что оно отвечает:

cd /srv/api
sudo -u api venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000
curl http://127.0.0.1:8000/health

Настройки приложения для продакшена

По умолчанию FastAPI открыто показывает полное описание API на страницах /docs и /redoc. Для внутреннего или коммерческого API это лишняя информация, поэтому в продакшене их лучше отключить или открыть только для определённых IP-адресов. Для мониторинга добавьте простую точку проверки здоровья.

import os
from fastapi import FastAPI

is_prod = os.getenv("APP_ENV") == "production"

app = FastAPI(
    docs_url=None if is_prod else "/docs",
    redoc_url=None if is_prod else "/redoc",
)

@app.get("/health")
def health():
    return {"status": "ok"}

Ещё один важный момент: если внутри эндпоинта, объявленного через async def, выполняется блокирующий код — синхронный HTTP-запрос или синхронный драйвер базы данных, — весь рабочий процесс ждёт, и остальные запросы тоже замедляются. В таких случаях либо используйте асинхронную библиотеку, либо объявите эндпоинт обычным def: тогда FastAPI выполнит его в отдельном пуле потоков.

Uvicorn как сервис systemd

Сервис должен автоматически подниматься после перезагрузки и перезапускаться после падения. Создайте файл /etc/systemd/system/api.service:

[Unit]
Description=FastAPI (Uvicorn) for api.misol.uz
After=network.target

[Service]
User=api
Group=api
WorkingDirectory=/srv/api
EnvironmentFile=/srv/api/.env
ExecStart=/srv/api/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 2 --proxy-headers --forwarded-allow-ips=127.0.0.1
Restart=on-failure

[Install]
WantedBy=multi-user.target

--host 127.0.0.1 делает сервис доступным только изнутри сервера, снаружи к нему можно попасть лишь через Nginx. --proxy-headers и --forwarded-allow-ips велят доверять заголовкам X-Forwarded-For и X-Forwarded-Proto от Nginx, и приложение видит реальный IP клиента и то, что соединение идёт по HTTPS. Число воркеров подбирается по количеству ядер процессора. Включите сервис:

sudo systemctl daemon-reload
sudo systemctl enable --now api
sudo journalctl -u api -f

Прокси Nginx и WebSocket

WebSocket-соединение начинается с обычного HTTP-запроса с обновлением протокола, поэтому Nginx должен пропускать заголовки Upgrade и Connection. Сначала в файле /etc/nginx/conf.d/upgrade.conf объявляется вспомогательная переменная:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

Затем пишется конфигурация сайта /etc/nginx/sites-available/api:

server {
    listen 80;
    server_name api.misol.uz;
    client_max_body_size 10M;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 300s;
    }
}
sudo ln -s /etc/nginx/sites-available/api /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d api.misol.uz

proxy_read_timeout не даёт Nginx обрывать через минуту долгоживущие WebSocket-соединения вроде отслеживания курьера. Перед получением сертификата A-запись поддомена api должна указывать на адрес VPS; добавить её можно в разделе DNS кабинета sayt.uz.

Обновления и поиск проблем

Выпуск новой версии выглядит просто: обновить код, поставить зависимости, при необходимости выполнить миграции базы и запустить sudo systemctl restart api. Если Nginx возвращает 502, сначала загляните в логи journalctl -u api: чаще всего неверен путь к модулю, в .env не хватает переменной или порт занят другим процессом. Если внутри приложения IP клиента всегда выглядит как 127.0.0.1, проверьте параметр --proxy-headers. В примере с чайханой такой порядок позволяет безопасно обновляться даже перед обедом: сервис поднимается за несколько секунд, и мобильное приложение этого почти не замечает.

Похожие статьи

🤖 Python-бот 24/7: запуск через systemd и PM2 🧠 Нехватка памяти (OOM): почему завершаются процессы PHP и Python 🧠 «Allowed memory size exhausted»: ошибка памяти PHP и как её правильно решить 🚀 Полностраничный кеш: когда он полезен, а когда мешает
🌐 Язык
🇺🇿 O'zbek 🇺🇿 Ўзбек 🇷🇺 Русский ✓ 🇬🇧 English