FastAPI за Nginx: /docs открывается, а openapi.json возвращает 404

Приложение успешно работает на localhost:8000/docs. Снаружи его разместили под example.com/api, и страница Swagger вроде бы открылась. Но вместо методов появилась ошибка загрузки схемы. Обычно дальше начинают менять CORS, хотя проблема возникает ещё до вызова API: браузер скачивает openapi.json не по тому адресу.

Здесь есть два пути. Пользователь видит внешний /api/docs. Прокси может убрать /api и передать приложению /docs. FastAPI нужно знать об этом внешнем префиксе, чтобы построить ссылки для браузера.

Одна согласованная схема маршрутов

Пример для Nginx, который работает на том же сервере и передаёт запросы локальному Uvicorn:

location /api/ {
    proxy_pass http://127.0.0.1:8000/;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Завершающий слеш в proxy_pass здесь важен: префикс location заменяется на /, и внешний /api/docs приходит как /docs. В FastAPI задаём root_path:

from fastapi import FastAPI

app = FastAPI(root_path="/api")

@app.get("/items")
def items():
    return [{"id": 1}]

Маршрут объявлен как /items. Не добавляйте /api ещё раз в каждый декоратор только потому, что его видит внешний клиент. Установка зависимостей: python -m pip install fastapi uvicorn; локальный запуск — python -m uvicorn main:app --host 127.0.0.1.

Проверка от браузера к приложению

Откройте инструменты разработчика, вкладку Network, затем /api/docs. Посмотрите точный URL неудачного запроса схемы. В этой схеме нужен /api/openapi.json с ответом 200. После загрузки схемы проверьте вызов /api/items через Swagger и отдельным HTTP-запросом.

Если браузер просит /openapi.json, приложение не учло внешний префикс. Если просит /api/api/openapi.json, префикс, вероятно, добавили дважды. Если правильный внешний путь даёт 404, проверьте, что именно прокси передаёт upstream.

Префикс и HTTPS — разные настройки

root_path сообщает о месте приложения под внешним путём. Протокол HTTPS и исходный адрес клиента передаются через proxy-заголовки и требуют доверия к вашему прокси. Не разрешайте произвольным внешним клиентам подменять эти данные: ограничьте доступ к upstream и настройте доверенные адреса.

Если прокси находится в контейнере, 127.0.0.1 внутри него указывает на сам контейнер. Для Compose понадобится адрес сервиса приложения в общей сети. Это отдельная настройка от root_path.

Более подробно изменение URI разобрано в статье про слеш в proxy_pass.

Собрать следующий этап проекта

Хотите собрать API целиком, а не останавливаться на отдельном обработчике? На бесплатном курсе FastAPI вы сделаете сервис с базой, регистрацией, проверкой прав и тестами. Это проект, который можно развивать дальше и показывать вместе с исходниками.

Посмотреть программу: FastAPI для начинающих: API с базой данных и тестами

Проверить детали: FastAPI за прокси, Nginx proxy_pass.

Продолжить на практике

  • FastAPI для начинающих: API с базой данных и тестами ↗

    Соберёте свой первый полноценный backend на FastAPI: с базой данных, регистрацией, JWT-авторизацией и автотестами. 34 коротких урока и 140 заданий с проверкой — бесплатно.

  • DevOps-инженер с нуля: полный курс ↗

    Практический курс по DevOps для начинающих: от настройки Linux и сетей до сборки контейнеров, автоматизации в GitLab CI/CD, развёртывания в Kubernetes, мониторинга и восстановления данных на собственном компьютере.