Как развернуть FastAPI на VPS с Uvicorn, systemd и Nginx
Устанавливаем FastAPI в Python-окружение, запускаем Uvicorn как службу и подключаем Nginx. Проверяем JSON, валидацию и документацию 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.
На стенде этот пример проверен за Nginx на отдельном loopback-порту с просмотром через SSH-туннель. Новый домен и публичный TLS-сертификат для опыта не выпускались. Сценарии запроса JSON, ошибки 422, настоящей 404 и перезапуска службы выполнены фактически.
Как выпускать изменения
Сохраните текущий код и набор зависимостей. Проверьте новую версию на отдельном экземпляре, включая миграции, если они есть. После переноса изменений используйте systemctl restart hostfera-fastapi.service и повторите основные запросы.
Для API с базой понадобится проверяемая резервная копия данных; сохранённый requirements.lock её не заменяет. Если Nginx начинает отдавать 502, используйте отдельную инструкцию по диагностике, чтобы проверить процесс, порт и журнал до изменения настроек наугад.
Подключение PostgreSQL
Следующий шаг для API с постоянными данными — PostgreSQL на том же VPS. Руководство показывает базу, роль и проверку подключения; модели, драйвер и миграции вашего приложения выбираются отдельно.
Первоисточники
Сообщите адрес статьи и фрагмент через контакты редакции.


