1. 1
  2. 2
  3. 3
  4. 4
  5. 5

# теория · шаг 2 из 5

Несколько сервисов на одном домене

Коротко

Один домен, несколько сервисов, раскладка по префиксам. Сервис за прокси не знает своего публичного адреса, и это вылезает в редиректах и ссылках.

  • Запрос /grafana без слеша nginx сам отдаёт 301 на /grafana/. Строка location = /grafana { return 301 ...; } из чужих конфигов не нужна.
  • В этом редиректе nginx подставляет свою схему и свой порт. За TLS-прокси это даёт http:// вместо https://; лечит absolute_redirect off.
  • Location от бэкенда чинит proxy_redirect, и он работает по умолчанию.
  • Своё правило proxy_redirect отменяет умолчание: одна добавленная строка ломает те редиректы, что работали сами.

Если знаешь про автоматический редирект на слеш и про то, что своё правило proxy_redirect отменяет умолчание - листай до «Теперь сам».

Сначала ответь сам

/etc/nginx/conf.d/shop.local.confhttp server
location /grafana/ { proxy_pass http://grafana:3000/; }
location /api/     { proxy_pass http://app:8000;      }
location /         { try_files $uri /index.html;      }

Два вопроса. Первый: что получит человек, набравший shop.local/grafana без последнего слеша, - страницу фронтенда из location / или что-то другое? Второй: grafana отвечает редиректом Location: http://grafana:3000/d/home - имя, которого в интернете не существует. Куда уедет браузер?

Ответы: 301 на /grafana/ и http://shop.local/grafana/d/home. Оба сделал nginx, ни одной строки для этого писать не пришлось.

На что это похоже

Здание с одной вывеской и несколькими фирмами внутри. Снаружи все они - «Полка», и посетитель приходит по общему адресу.

Внутри же каждая фирма живёт своей жизнью и печатает бланки со своим внутренним номером: «продолжение разговора - в комнате 312». Посетитель с таким бланком выходит на улицу и не находит никакой комнаты 312: снаружи есть только вывеска и вход.

Работа администратора на входе - переписывать такие бланки в наружные адреса. Он умеет это для комнат, которые сам же и обслуживает, и не умеет для адресов, которые фирма придумала сама.

Механизм: две стороны одного запроса

браузер nginx grafana:3000 /grafana/d /d Location: http://grafana:3000/d/home Location: http://shop.local/grafana/d/home запрос вниз: префикс срезается по правилу proxy_pass ответ вверх: адрес из proxy_pass в Location меняется на публичный внутри тела ответа не меняется ничего
Обратно переписывается только заголовок Location. HTML едет как есть.

Правило

Форма proxy_pass выбирается для каждого сервиса своя, и приводить соседние строки «к одному виду» нельзя:

/etc/nginx/conf.d/shop.local.confhttp server
location /api/     { proxy_pass http://app:8000;      }   # маршруты сервиса начинаются с /api/
location /grafana/ { proxy_pass http://grafana:3000/; }   # сервис живёт в корне, префикс наш

Всё, что не совпало ни с одним сервисом, достаётся location /. Выбор идёт по правилам четвёртого раздела: побеждает самый длинный совпавший префикс.

Разбор: редирект на слеш nginx делает сам

Стенд, location /grafana/ с proxy_pass, запрос без последнего слеша:

$  команда
curl -sI http://shop.local/grafana
↳  выводcurl -sI http://shop.local/grafana
HTTP/1.1 301 Moved Permanently
Server: nginx/1.31.5
Date: Thu, 10 Sep 2026 20:20:28 GMT
Content-Type: text/html
Content-Length: 169
Location: http://shop.local/grafana/
Connection: keep-alive

Так ведёт себя блок-префикс, кончающийся слешем: nginx помечает его флагом автоматического редиректа. Проверено и с location / рядом, и без него, и с обеими формами proxy_pass - результат один. Строку location = /grafana { return 301 /grafana/; }, которая кочует по руководствам, писать не нужно.

Зато у этого редиректа есть цена. Адрес nginx собирает сам, из своей схемы и своего порта:

конфиг Location
listen 80 http://shop.local/grafana/
listen 8080 http://shop.local:8080/grafana/
absolute_redirect off /grafana/

Вторая строка - готовая беда за TLS-прокси (чужим сервером, который принимает HTTPS и дальше передаёт обычный HTTP): внутренний порт уедет наружу, а схема будет http даже там, где сайт живёт на https. Поэтому за чужим терминатором TLS ставят absolute_redirect off;http, server или location) - тогда nginx отдаёт относительный адрес, и браузер достраивает его сам, верной схемой.

Разбор: своё правило отменяет умолчание

proxy_redirect по умолчанию стоит в значении default и переписывает Location, если тот совпал с адресом из proxy_pass. Работает без настройки. Проблема начинается, когда сервис отдаёт свой собственный публичный адрес из внутренних настроек, - такой строки в proxy_pass нет, и умолчание её не трогает.

Пишут явное правило: первый аргумент - что искать в начале Location, второй - на что заменить. Стенд, два одинаковых блока, отличающихся только этой строкой; бэкенд app:8000 отвечает редиректом то на свой адрес, то на shop.example.com:

/etc/nginx/conf.d/shop.local.confhttp server
location /g1/ { proxy_pass http://app:8000/; }
location /g3/ { proxy_pass http://app:8000/; proxy_redirect http://shop.example.com/ /g3/; }

Относительный адрес из правила (/g3/) nginx достраивает до полного своим именем и схемой - отсюда shop.local в результате:

блок Location: http://app:8000/d/home Location: http://shop.example.com/d/home
без своего правила переписан в http://shop.local/g1/d/home не тронут
со своим правилом не тронут переписан в http://shop.local/g3/d/home

Правило не добавилось к умолчанию, а заменило его. Починив один редирект, ты сломал те, что работали сами. Лечится строкой proxy_redirect default; рядом со своим правилом - тогда работают оба.

Что ломается без этого

proxy_redirect чинит один заголовок. Адреса внутри HTML он не трогает вовсе, а их у любой панели сотни: <script src="/public/build/app.js">, <link href="/public/css/main.css">. Браузер запросит их от корня домена, попадёт в location / к фронтенду и получит index.html вместо скрипта. Симптом узнаваемый: страница открылась, но без стилей и без данных, а в консоли Unexpected token '<' (консоль открывается в браузере клавишей F12).

Чинится это не в nginx. Почти у каждого приложения есть настройка публичного адреса: root_url у Grafana, FORCE_SCRIPT_NAME у Django, base href у фронтендов, APP_URL у Laravel. Выставил её - и ссылки, и редиректы становятся верными в один заход.

Зачем это в работе

Внутренние панели почти всегда вешают на префикс общего домена: отдельное имя означает отдельный сертификат (файл, которым сайт подтверждает своё имя по HTTPS) и отдельную запись в DNS (связь имени с адресом). Значит, в любом таком конфиге встретятся все три механизма разом: срезание префикса на входе, автоматический редирект на слеш и переписывание Location на выходе.

Первым делом при разборе «панель открылась криво» сравни путь, который ты запросил, с путём в логе самого сервиса (у контейнера - docker compose logs grafana, у службы - её собственный лог). Префикс /grafana/ доехал до бэкенда - слеш в proxy_pass потерян. Путь верный, а страница пустая - смотри ссылки в HTML и настройку публичного адреса у самого сервиса.

Теперь сам

К домену добавили location /kibana/ { proxy_pass http://kibana:5601/; }. Kibana отвечает Location: http://shop.local/app/home - свой публичный адрес она берёт из собственного конфига, где префикс не указан. Что увидит человек и что править?

Умолчание proxy_redirect этот адрес не узнает: в proxy_pass написан kibana:5601, а в заголовке стоит shop.local. Браузер уедет на /app/home, попадёт в location / и получит фронтенд. Правильный ход - выставить префикс в самой Kibana (server.basePath), потому что кроме Location есть ещё и ссылки в HTML. Заплатка на время правки - proxy_redirect http://shop.local/app/ /kibana/app/; плюс proxy_redirect default; рядом, чтобы не потерять умолчание.

Главное

Сервисы разводят по префиксам, и форма proxy_pass у каждого своя. Запрос без последнего слеша nginx перенаправляет сам, но собирает адрес из своей схемы и порта - за TLS-прокси нужен absolute_redirect off. Заголовок Location от бэкенда переписывается по умолчанию, пока адрес в нём совпадает с адресом из proxy_pass; своё правило proxy_redirect это умолчание отменяет, поэтому рядом дописывают proxy_redirect default. Ссылки внутри HTML не чинит ничто из этого - их лечит настройка публичного адреса в самом приложении.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.