# теория · шаг 2 из 6
return: коды, тела, редиректы
Коротко
return отвечает сам, не трогая ни диск, ни бэкенд. Форм у неё четыре, и
четвёртая - голый адрес без кода - молча даёт 302, а не 301. Работает она на
фазе rewrite, то есть раньше deny, limit_req и auth_basic: строка
return 200 в тестовом блоке отменяет всю защиту, которую ты рядом настроил.
Для редиректа берут $request_uri, а не $uri: второй теряет строку
параметров.
Знаешь эту директиву - листай до «Теперь сам». Разборы нужны, если ловил 200 там, где ждал 403.
Вот блок из настоящего конфига. Как думаешь, что получит клиент с чужого адреса?
location /вход {
deny all;
return 200 "проверка связи\n";
}
deny all стоит первой строкой и написан явно. На стенде 1.31.5 этот блок
отдаёт 200 и текст проверки. Не 403.
На что это похоже
Представь турникет в бизнес-центре и охранника перед ним. Охранник стоит
раньше турникета: если он выдаёт пропуск сам, до турникета человек просто не
доходит - и неважно, что турникет настроен и работает. return в nginx стоит
на такой же позиции: он срабатывает на фазе rewrite, а deny, лимиты и пароль
живут на фазах, которые идут позже.
Механизм: где на конвейере стоит return
Отсюда и правило про тестовые заглушки: блок, который «просто отвечает ok», проверяет не то, что ты думаешь. Он проверяет, что nginx жив, - и ничего больше.
Правило
Второй аргумент читается по коду. Код из редиректных (301, 302, 303, 307, 308) - значит адрес. Любой другой код - значит тело ответа. Кода нет вовсе - значит адрес, и ответ будет 302.
return 403; # код без тела
return 200 "pong\n"; # код и тело
return 301 https://shop.local$request_uri; # код и адрес
return https://shop.local/; # адрес без кода = 302
Разбор: четыре формы на стенде
Проверено на 1.31.5, по одному location на форму:
| запись | строка ответа | Location | тело |
|---|---|---|---|
return 200 "тело" |
200 OK |
нет | тело |
return 404 |
404 Not Found |
нет | стандартная страница |
return 301 /new |
301 Moved Permanently |
http://localhost/new |
страница редиректа |
return https://short.example/ |
302 Moved Temporarily |
https://short.example/ |
страница редиректа |
return 444 |
соединения нет | - | curl отвечает кодом 52 |
return 418 |
418 без названия |
нет | пусто |
Две строки заслуживают внимания. Голый адрес дал 302, хотя писавший почти
всегда имеет в виду постоянный переезд. И относительный путь /new уехал
клиенту как абсолютный http://localhost/new - адрес собран из схемы, Host
и пути, поэтому за чужим терминатором TLS туда попадёт http://, если не
поправить absolute_redirect или не написать адрес целиком.
Разбор: почему редирект теряет метки маркетинга
Два одинаковых на вид редиректа, замер на одном и том же запросе
/red?x=1:
location = /red-uri { return 301 $uri; }
location = /red-req { return 301 $request_uri; }
curl -sI 'http://localhost/red-uri?x=1' | grep -i location
curl -sI 'http://localhost/red-req?x=1' | grep -i location
Location: http://localhost/red-uri
Location: http://localhost/red-req?x=1
$uri - это нормализованный путь без строки параметров, да ещё и
изменённый предыдущими перезаписями. $request_uri - то, что клиент прислал,
целиком. Редирект на $uri работает: страница открывается, код 301, в
мониторинге всё зелено. Пропадает только ?utm_source=..., и замечают это
через месяц по отчёту, в котором переходы из рассылки стали нулевыми.
Что ломается без этого
- Заглушка отменяет защиту.
deny allрядом сreturn 200не срабатывает: замер даёт 200 против 403 у того же блока безreturn. То же сlimit_reqиauth_basic. - Проверка здоровья считает себя проверкой.
location = /healthz { return 200 "ok"; }отвечает «ok», даже когда бэкенд лежит: до бэкенда запрос не идёт. Это нормально для проверки самого nginx и бесполезно как проверка приложения. error_page 404 = 200 /empty.jsonне грузится. nginx отвечаетinvalid value "=": код прирастает к знаку равенства без пробела, верная запись -=200. Форма= @nameс пробелом при этом законна, потому что там кода нет.- Отсутствующая страница ошибки не роняет сервер.
error_page 404 /нет-такой.htmlпри отсутствующем файле отдаёт обычную стандартную страницу 404, а вerror_logпоявляетсяopen() ... failed (2: No such file or directory). Ни цикла, ни 500.
Зачем это в работе
Три места, где return - самый честный инструмент.
location = /healthz {
access_log off;
default_type application/json;
return 200 '{"status":"ok"}';
}
Проверка здоровья ходит раз в секунду - за сутки это под сотню тысяч строк в
логе, поэтому access_log off тут не украшение.
server {
listen 80 default_server;
server_name _;
return 444;
}
Блок по умолчанию, который закрывает соединение молча. Сканер видит не сайт, а отсутствие сервера. От целевой атаки это не защищает, но убирает твой сайт из чужих списков «что живёт на этом адресе».
И третье - переезд домена одной строкой, без регулярки на каждый запрос:
return 301 https://новый-домен.ru$request_uri;
Теперь сам
Блок отвечает не тем, чего от него ждут. Что увидит клиент и почему?
location /api/ {
limit_req zone=one burst=5;
return 200 "api\n";
}
Ответ: все запросы получат 200, лимит не сработает ни разу. return живёт на
фазе rewrite, limit_req - на preaccess, а до неё дело не дойдёт. Чтобы лимит
заработал, содержимое должен отдавать кто-то на фазе контента: proxy_pass,
try_files или файл с диска.
Главное
return отвечает раньше доступа и лимитов - поэтому заглушка с return 200
делает соседние deny и limit_req бессмысленными. Адрес без кода даёт 302.
В редиректе берут $request_uri, иначе теряется строка параметров.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий