# теория · шаг 2 из 5
Несколько сервисов на одном домене
Коротко
Один домен, несколько сервисов, раскладка по префиксам. Сервис за прокси не знает своего публичного адреса, и это вылезает в редиректах и ссылках.
- Запрос
/grafanaбез слеша nginx сам отдаёт 301 на/grafana/. Строкаlocation = /grafana { return 301 ...; }из чужих конфигов не нужна. - В этом редиректе nginx подставляет свою схему и свой порт. За TLS-прокси это
даёт
http://вместоhttps://; лечитabsolute_redirect off. Locationот бэкенда чинитproxy_redirect, и он работает по умолчанию.- Своё правило
proxy_redirectотменяет умолчание: одна добавленная строка ломает те редиректы, что работали сами.
Если знаешь про автоматический редирект на слеш и про то, что своё правило proxy_redirect отменяет умолчание - листай до «Теперь сам».
Сначала ответь сам
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: снаружи есть только вывеска и вход.
Работа администратора на входе - переписывать такие бланки в наружные адреса. Он умеет это для комнат, которые сам же и обслуживает, и не умеет для адресов, которые фирма придумала сама.
Механизм: две стороны одного запроса
Правило
Форма proxy_pass выбирается для каждого сервиса своя, и приводить соседние
строки «к одному виду» нельзя:
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
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:
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 не чинит ничто из
этого - их лечит настройка публичного адреса в самом приложении.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий