VPS

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

Устанавливаем FastAPI в Python-окружение, запускаем Uvicorn как службу и подключаем Nginx. Проверяем JSON, валидацию и документацию API. Готовый пример для скачивания.

Приложение FastAPI с проверкой состояния и документацией API

FastAPI-приложение на VPS нужно установить в отдельное Python-окружение, запустить как службу и соединить с веб-сервером. Развернём рабочий пример на Ubuntu 24.04: Uvicorn будет слушать локальный порт 8000, а Nginx — принимать запросы посетителей.

Что проверим

У приложения три основные точки входа: HTML-страница /, проверка состояния /healthz и POST /sum, складывающий два числа из JSON. Некорректный запрос должен получить 422, а отсутствующая страница — 404. Это позволяет проверить запуск Python, маршрутизацию и валидацию данных.

Испытано 29 октября 2026 года: Ubuntu 24.04.5 LTS, Python 3.12.3, FastAPI 0.162.0 и Uvicorn 0.54.0. Зависимости из проверенного окружения находятся в requirements.lock внутри примера.

Нужен доступ по SSH с sudo, свободный порт 8000 и домен для будущего публичного доступа. Если выбираете сервер, начните с руководства по конфигурации VPS. Эти команды предназначены для нового проекта; существующие пути и учётные записи сначала проверьте.

Подготовьте Python и файлы приложения

Скачать пример FastAPI с unit-файлом, Nginx и зависимостями.

sudo -i
apt-get update
apt-get install -y python3-venv curl unzip nginx
test ! -e /opt/hostfera-fastapi/venv || { echo 'Окружение уже существует'; exit 1; }
TEMP=$(mktemp -d)
curl --fail --show-error --location --proto '=https' \
  https://hostfera.ru/static/downloads/hostfera-fastapi-example-1.0.0.zip \
  -o "$TEMP/fastapi-example.zip"
printf '%s  %s\n' 'edc369d33cb1bf65ca7259de2e33fdaad99b4d039aacb9d4766b8bccb03839c6' "$TEMP/fastapi-example.zip" | sha256sum --check -
unzip "$TEMP/fastapi-example.zip" -d "$TEMP/example"
install -d -m 755 /opt/hostfera-fastapi/app
install -m 644 "$TEMP/example/hostfera-fastapi-example/main.py" \
  /opt/hostfera-fastapi/app/main.py
install -m 644 "$TEMP/example/hostfera-fastapi-example/requirements.lock" \
  /opt/hostfera-fastapi/app/requirements.lock
python3 -m venv /opt/hostfera-fastapi/venv
/opt/hostfera-fastapi/venv/bin/python -m pip install \
  -r /opt/hostfera-fastapi/app/requirements.lock
/opt/hostfera-fastapi/venv/bin/python -m pip check

Установка через venv отделяет зависимости приложения от системного Python. Путь /opt/hostfera-fastapi/venv/bin/python важно использовать и для установки пакетов, и для запуска сервера.

Файл зависимостей фиксирует версии из испытания, но не является обещанием их бессрочной актуальности. При обновлении проекта проверяйте совместимость и исправления безопасности, затем сохраняйте новый проверенный набор.

Как устроен пример

Содержимое main.py:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from pydantic import BaseModel, ConfigDict, Field

app = FastAPI(title="Hostfera API example", version="1.0.0")

class Numbers(BaseModel):
    model_config = ConfigDict(extra="forbid")
    a: int = Field(ge=-1_000_000, le=1_000_000)
    b: int = Field(ge=-1_000_000, le=1_000_000)

@app.get("/healthz")
def health():
    return {"status": "ok", "service": "hostfera-fastapi"}

@app.post("/sum")
def calculate(data: Numbers):
    return {"result": data.a + data.b}

@app.get("/", response_class=HTMLResponse)
def index():
    return """<!doctype html><html lang="ru"><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>FastAPI на VPS — практика Hostfera</title>
<style>body{font:18px/1.6 system-ui;margin:0;background:#f0f5ff;color:#17305b}main{max-width:760px;margin:9vh auto;padding:32px}h1{font-size:clamp(32px,7vw,56px);line-height:1.1}section{padding:24px;background:#fff;border:1px solid #c1d2f0;border-radius:18px}a{color:#2454a8}code{overflow-wrap:anywhere}</style>
<main><small>HOSTFERA / ПРАКТИКА PYTHON</small><h1>FastAPI принимает<br>ваши запросы.</h1>
<section><p>Uvicorn запущен отдельной службой systemd за Nginx.</p>
<p><a href="/healthz">Проверка состояния →</a> · <a href="/docs">Документация API →</a></p>
<p>Тестовый запрос: <code>POST /sum {"a": 2, "b": 3}</code>. Ожидаемый результат: 5.</p></section></main></html>"""

Модель Numbers задаёт обязательные поля, диапазон значений и запрет дополнительных полей. Данный пример не сохраняет запросы в базу и не выполняет внешних действий.

Для реального API авторизация, правила доступа, миграции базы и ограничения нагрузки определяются задачей проекта. Доступная страница /docs полезна при разработке, но её публичность тоже стоит выбирать осознанно. Не добавляйте в примеры документации рабочие токены.

Создайте отдельного пользователя и службу

useradd --system --user-group --no-create-home --shell /usr/sbin/nologin hf_fastapi
install -m 644 "$TEMP/example/hostfera-fastapi-example/hostfera-fastapi.service" \
  /etc/systemd/system/hostfera-fastapi.service
[Unit]
Description=Hostfera FastAPI example
After=network.target

[Service]
Type=simple
User=hf_fastapi
Group=hf_fastapi
WorkingDirectory=/opt/hostfera-fastapi/app
ExecStart=/opt/hostfera-fastapi/venv/bin/python -m uvicorn main:app --host 127.0.0.1 --port 8000 --workers 1 --proxy-headers --forwarded-allow-ips=127.0.0.1
Restart=on-failure
RestartSec=3
TimeoutStopSec=15
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
MemoryMax=256M
UMask=0027

[Install]
WantedBy=multi-user.target

Uvicorn запускается одним рабочим процессом. Параметр --reload не используется: он предназначен для удобства разработки, а службой управляет systemd. Значение --forwarded-allow-ips=127.0.0.1 ограничивает доверие к заголовкам прокси локальным Nginx.

В примере нет записи файлов. Если приложение загружает документы или использует локальную базу, создайте отдельный каталог данных и разрешите запись именно в него через параметры systemd. Код и venv не нужно делать доступными для изменения всем пользователям.

systemctl daemon-reload
systemctl enable --now hostfera-fastapi.service
systemctl status hostfera-fastapi.service --no-pager
curl --retry 5 --retry-connrefused --retry-delay 1 --fail http://127.0.0.1:8000/healthz

Ожидаемый JSON: {"status":"ok","service":"hostfera-fastapi"}. При ошибке откройте журнал:

journalctl -u hostfera-fastapi.service -n 50 --no-pager
ss -ltnp 'sport = :8000'

Убедитесь, что процесс использует правильное окружение и слушает 127.0.0.1:8000. Если Uvicorn сообщает, что не может импортировать main, проверьте WorkingDirectory, наличие main.py и имя объекта app.

Подключите Nginx к Uvicorn

Скопируйте конфигурацию и замените api.example.com на свой домен:

test ! -e /etc/nginx/sites-available/hostfera-fastapi || { echo 'Файл уже существует'; exit 1; }
install -m 644 "$TEMP/example/hostfera-fastapi-example/nginx.conf" \
  /etc/nginx/sites-available/hostfera-fastapi
server {
    listen 80;
    server_name api.example.com;
    access_log /var/log/nginx/hostfera-fastapi.access.log;
    error_log /var/log/nginx/hostfera-fastapi.error.log;
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/hostfera-fastapi-acme;
        try_files $uri =404;
    }
    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 $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 5s;
        proxy_read_timeout 30s;
    }
}

Включите сайт после редактирования имени:

install -d -m 755 /var/www/hostfera-fastapi-acme
ln -s /etc/nginx/sites-available/hostfera-fastapi /etc/nginx/sites-enabled/hostfera-fastapi
nginx -t
systemctl reload nginx
curl --resolve api.example.com:80:127.0.0.1 http://api.example.com/healthz

Проверка --resolve обращается к локальному серверу с указанным именем виртуального хоста. Для посетителей настройте DNS по гайду привязки домена. Имя в команде и server_name должны совпадать.

Проверьте JSON и ошибки валидации

Проверьте API напрямую на VPS:

curl -i http://127.0.0.1:8000/sum \
  -H 'Content-Type: application/json' \
  -d '{"a":2,"b":3}'

curl -i http://127.0.0.1:8000/sum \
  -H 'Content-Type: application/json' \
  -d '{"a":"wrong","b":3}'

Первый запрос возвращает 200 и {"result":5}. Второй получает 422: он дошёл до FastAPI, но не соответствует модели данных. Это отличается от 502, когда Nginx не смог получить корректный ответ от приложения.

Повторите запрос через свой домен, чтобы проверить и Nginx. Откройте /docs и убедитесь, что схема загружается, а не только отображается оболочка страницы. Если выбран собственный префикс вроде /api/, потребуется отдельно согласовать маршруты и root_path; данный пример опубликован в корне домена.

HTTPS и внешний доступ

Получите сертификат по инструкции Let's Encrypt, используя webroot /var/www/hostfera-fastapi-acme. При настройке TLS сохраните проксирование location / на Uvicorn и передачу X-Forwarded-Proto.

Снаружи нужен доступ к 80/443; порт 8000 остаётся локальным. Проверьте правила UFW и сертификат. После подключения HTTPS повторите запрос с JSON и проверку схемы API.

Приложение FastAPI с проверкой состояния и документацией API
FastAPI за Nginx: фактически запущенный пример

На стенде этот пример проверен за Nginx на отдельном loopback-порту с просмотром через SSH-туннель. Новый домен и публичный TLS-сертификат для опыта не выпускались. Сценарии запроса JSON, ошибки 422, настоящей 404 и перезапуска службы выполнены фактически.

Как выпускать изменения

Сохраните текущий код и набор зависимостей. Проверьте новую версию на отдельном экземпляре, включая миграции, если они есть. После переноса изменений используйте systemctl restart hostfera-fastapi.service и повторите основные запросы.

Для API с базой понадобится проверяемая резервная копия данных; сохранённый requirements.lock её не заменяет. Если Nginx начинает отдавать 502, используйте отдельную инструкцию по диагностике, чтобы проверить процесс, порт и журнал до изменения настроек наугад.

Подключение PostgreSQL

Следующий шаг для API с постоянными данными — PostgreSQL на том же VPS. Руководство показывает базу, роль и проверку подключения; модели, драйвер и миграции вашего приложения выбираются отдельно.

Первоисточники

Нашли неточность?

Сообщите адрес статьи и фрагмент через контакты редакции.