Reverse proxy: proxy_pass и проброс запроса

Рейтинг: 60.1% · 14 голосов
Самый подробный курс по Nginx - от первого конфига до production API-gateway. Архитектура и event loop, контексты и location, виртуальные хосты, статика, reverse proxy и upstream-балансировка, кэширование, HTTPS и TLS (post-quantum), HTTP/2 и HTTP/3 QUIC, безопасность и rate limiting, realip, JSON-логи и сквозная трассировка, метрики, тюнинг и траблшутинг. Плюс OpenResty (Lua, фазы, JWT, динамика) и Angie (нативные Prometheus, ACME, dynamic upstream). На основе эталонного gateway и лучших практик. Для новичков и SRE/DevOps. Актуально на 2026 (nginx 1.30/1.31, Angie, ingress-nginx EOL -> Gateway API).
Ответить
Аватара пользователя
Igor_NgINX
Сообщения: 44
Зарегистрирован: 11 май 2026, 05:31

Reverse proxy: proxy_pass и проброс запроса

Сообщение Igor_NgINX »

Оглавление курса (44)
  1. Что такое Nginx и почему он захватил веб
  2. Установка Nginx, Angie и OpenResty: пакеты, версии, Docker
  3. Первый конфиг с нуля: минимальный сервер за 5 минут
  4. Архитектура процессов: master, worker и event loop
  5. Анатомия конфигурации: контексты, наследование и модульность
  6. Виртуальные хосты: server и server_name
  7. Директива location: матчинг и приоритеты по шагам
  8. Раздача статики: root, alias, try_files, sendfile
  9. Переменные, map и блок if
  10. rewrite, return и канонизация URL
  11. Работа с заголовками: add_header и proxy_set_header
  12. Модель доверия прокси-заголовков: realip и защита от спуфинга
  13. Сжатие: gzip, brotli и zstd
  14. Reverse proxy: proxy_pass и проброс запроса (вы здесь)
  15. upstream и балансировка нагрузки
  16. Таймауты, повторы и устойчивость прокси
  17. FastCGI и PHP: nginx + PHP-FPM (и uwsgi/scgi)
  18. WebSocket, gRPC и стриминг через Nginx
  19. Кэширование ответов: proxy_cache и микрокэш
  20. HTTPS и TLS: сертификаты, протоколы, шифры (2026)
  21. Let's Encrypt и ACME: certbot, acme.sh, нативный ACME Angie
  22. HTTP/2 и HTTP/3 (QUIC) в 2026
  23. Безопасность: заголовки, скрытие версии, ограничения
  24. Rate limiting и защита от перегрузки
  25. Контроль доступа: allow/deny, auth_basic, auth_request и mTLS
  26. Логирование: access_log, форматы и структурированный JSON
  27. Сквозная трассировка запросов и OpenTelemetry
  28. Метрики и мониторинг Nginx
  29. Производительность и тюнинг под нагрузку
  30. Траблшутинг: 502, 504, 403, 404 и debug-лог
  31. OpenResty: Nginx как платформа на LuaJIT
  32. Фазы обработки запроса и Lua-хуки
  33. Lua API: ngx.*, shared dict, cosocket и lua-resty-core
  34. Практика OpenResty: авторизация, JWT, кэш, Redis
  35. Angie: современный форк Nginx 2026
  36. Динамическая конфигурация и service discovery
  37. Nginx как API-gateway и Kubernetes Gateway API
  38. Динамические модули, njs и WAF
  39. Stream-модуль: проксирование TCP и UDP
  40. Деплой, перезагрузка и конфиг как код
  41. Сценарий: статика, SPA и кэширование за CDN
  42. Сценарий: микросервисный gateway по образцу ngx-trace-gateway
  43. Капстоун: собираем production-gateway с нуля
  44. Чек-лист безопасности, CVE 2026 и аудит конфигурации
Зачем вообще обратный прокси и при чём тут одна директива

В 2026 году главная роль Nginx - не раздавать статику (хотя он это умеет блестяще), а стоять на входе и разруливать трафик к бэкендам. Это и называется nginx reverse proxy, обратный прокси. Клиент стучится в один публичный адрес, видит один TLS-сертификат, один домен - а за спиной Nginx сидит зоопарк: PHP-FPM, Node, Go-сервис, питоновский gunicorn, ещё один Nginx. Прямой прокси (forward) работает за клиента и ходит в интернет от его имени. Обратный (reverse) работает за сервер: принимает запрос снаружи и решает, какому внутреннему апстриму его отдать. С точки зрения клиента бэкенда вообще не существует - есть только шлюз.

Боль, которую это решает, простая. Бэкенд не должен торчать наружу: у него нет нормального TLS, он не умеет rate limit, он падает под нагрузкой соединений, он не знает про твой домен. Nginx берёт на себя терминацию TLS, буферизацию медленных клиентов, балансировку, кэш, лимиты - а бэкенду отдаёт чистый, быстрый, предсказуемый HTTP. Вся эта магия начинается с одной директивы - nginx proxy_pass. Но именно вокруг неё больше всего граблей, потому что её поведение зависит от одного символа в конце строки. Разберём механику до винтика.

Изображение

nginx proxy_pass: слеш в конце меняет всё

Директива proxy_pass говорит: "этот запрос отдай туда". Пишется внутри location. И вот тут ключевое правило, которое ломает людям прод годами: есть ли в proxy_pass URI-часть (путь после хоста) или нет.

Случай первый - в proxy_pass нет пути, только схема и адрес:

Код: Выделить всё

location /api/ {
    proxy_pass http://127.0.0.1:9000;
}
Здесь после порта ничего нет. Nginx передаёт бэкенду URI как есть, целиком. Пришёл запрос /api/users -> бэкенд получит /api/users. Префикс location не отрезается. Это режим "проксируй полный путь".

Случай второй - в proxy_pass есть путь (даже если это просто слеш):

Код: Выделить всё

location /api/ {
    proxy_pass http://127.0.0.1:9000/;
}
Теперь Nginx работает иначе: он отрезает совпавший префикс location (/api/) и подставляет вместо него URI из proxy_pass (/). Пришёл /api/users -> бэкенд получит /users. Префикс /api/ съеден, на его место встал /.

Сравни на конкретике, запрос всегда GET /api/users/42:
  • proxy_pass http://up; (без пути) -> бэкенд видит /api/users/42
  • proxy_pass http://up/; (слеш) -> бэкенд видит /users/42
  • proxy_pass http://up/v2/; -> бэкенд видит /v2/users/42
  • proxy_pass http://up/v2; (без слеша на конце, с путём) -> бэкенд видит /v2users/42 - склейка без разделителя, классический баг
Последняя строка - любимые грабли. Ты написал /v2 без слеша, location был /api/, и Nginx тупо приклеил остаток users/42 к v2, получилось /v2users/42 -> 404. Поэтому правило руками: если в location есть слеш на конце - в proxy_pass тоже ставь слеш на конце, тогда пути склеиваются предсказуемо.

Важная оговорка: режим с отрезанием префикса не работает, если внутри location есть rewrite, который меняет $uri, или если location задан регэкспом (location ~). В этих случаях Nginx физически не знает, какую часть отрезать, и URI-часть в proxy_pass запрещена - надо передавать переменную или собирать путь самому. Это, кстати, ещё одна причина в 2026 минимизировать rewrite-правила: помимо граблей с proxy_pass, в ngx_http_rewrite_module нашли CVE-2026-42945 (heap overflow, "NGINX Rift"), так что лишний rewrite - это и риск, и путаница. Меньше переписывания пути - проще жизнь.

nginx проброс заголовков: что бэкенд должен узнать о клиенте

Как только запрос ушёл через прокси, бэкенд теряет связь с реальностью. Он видит соединение от Nginx, а не от клиента. Значит, $remote_addr на бэкенде - это IP Nginx. Host в запросе по умолчанию подменяется на адрес из proxy_pass. Схема (http/https) теряется - до бэкенда всё доходит как plain http, ведь TLS терминировал Nginx. Если бэкенду это важно (а ему почти всегда важно - для логов, редиректов, генерации абсолютных URL, rate limit, авторизации по IP) - мы обязаны рассказать ему правду заголовками. Это и есть nginx проброс заголовков.

Базовый джентльменский набор, как в global_proxy эталонного gateway:

Код: Выделить всё

proxy_set_header Host              $host;
proxy_set_header X-Real-IP         $remote_addr;
proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host  $host;
Разберём построчно, потому что каждая строка решает конкретную проблему.

nginx proxy_set_header Host $host - самое частое. Без него Nginx подставит в Host адрес апстрима (127.0.0.1:9000), и приложение с виртуальными хостами растеряется: куда пришли, на какой домен? $host - это имя из строки запроса или заголовка Host клиента. Вариант $http_host передаёт заголовок ровно как прислал клиент (с портом), $host - нормализованный. Для большинства задач $host - правильный выбор.

X-Real-IP $remote_addr - кладём настоящий IP клиента (на входном Nginx $remote_addr ещё настоящий) в отдельный заголовок. Бэкенд читает его и знает, кто пришёл.

X-Forwarded-For $proxy_add_x_forwarded_for - это умная переменная. Она берёт пришедший X-Forwarded-For и дописывает в конец текущий $remote_addr через запятую. Получается цепочка хопов: client, proxy1, proxy2. Если поставить просто $remote_addr - затрёшь историю при многоуровневом проксировании.

X-Forwarded-Proto $scheme - сообщает бэкенду исходную схему. Nginx терминировал TLS, к бэкенду идёт http, но клиент-то пришёл по https. Без этого заголовка фреймворк (Laravel, Django, Rails) сгенерирует http-ссылки и редиректы, словит mixed content и циклы редиректов. $scheme = http или https в зависимости от того, как клиент достучался до Nginx.

Критично про безопасность: эти заголовки имеют смысл, только если на самом входном прокси. Если твой Nginx стоит за CDN или другим прокси, $remote_addr - это уже не клиент, а соседний прокси. Тогда нужен модуль realip (set_real_ip_from на доверенные сети + real_ip_header X-Forwarded-For + real_ip_recursive on), иначе и X-Real-IP, и rate limit, и гео будут считать по чужому IP. И никогда не доверяй X-Forwarded-For от кого попало: клиент может прислать его сам и подделать свой IP. Доверяй только заголовкам от известных сетей прокси/CDN.

2026: keepalive к бэкенду теперь по умолчанию

Тут важное обновление, которое многие проспали. Раньше (до nginx 1.29.7) соединение от Nginx к бэкенду по умолчанию шло по HTTP/1.0 с Connection: close - то есть на каждый запрос новый TCP-коннект к апстриму, открыли-закрыли. Под нагрузкой это жрало порты, грело CPU на хендшейках и упиралось в TIME_WAIT. Чтобы включить переиспользование соединений, приходилось руками писать три вещи: keepalive в блоке upstream, proxy_http_version 1.1 и обнуление заголовка Connection.

С nginx 1.29.7 (вошло в stable 1.30.0, апрель 2026) поведение по умолчанию изменилось: соединение к бэкенду теперь HTTP/1.1 с включённым keepalive. По умолчанию кэшируется до 32 соединений на каждый воркер-процесс, и эти соединения не шарятся между разными location. Это заметно снижает накладные расходы на работу с бэкендами из коробки.

Но - и это важно для практики - ты всё равно должен держать в конфиге явную настройку. Причины две. Первая: ты не знаешь, на какой версии Nginx это поедет в проде или у коллеги - на 1.28 дефолт ещё старый. Вторая: дефолтные 32 коннекта на воркер часто маловато для нагруженного API. Поэтому канонический явный рецепт, который работает везде и однозначен:

Код: Выделить всё

upstream backend {
    server 10.0.0.1:9000 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:9000 max_fails=3 fail_timeout=30s;
    keepalive 64;
}

server {
    location /api/ {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}
Что тут что. keepalive 64 в upstream - держать пул до 64 живых соединений на воркер. proxy_http_version 1.1 - явно говорим HTTP/1.1 (keepalive в HTTP/1.0 не работает нормально). proxy_set_header Connection "" - обнуляем заголовок Connection: по умолчанию Nginx ставит Connection: close, что убивает keepalive; пустая строка означает "не отправлять close, держи соединение". В 2026 эти три строки - страховка совместимости, а не обязательная магия, как было раньше. Формулируй для команды честно: "на 1.30+ это дефолт, но мы пишем явно для старых версий и чтобы поднять лимит пула".

Отдельно про новый параметр keepalive ... local (появился вместе с этим изменением) - он управляет привязкой кэшированных соединений к локальному адресу. На обычном шлюзе не нужен, всплывает при хитрой маршрутизации с несколькими исходящими интерфейсами.

[p][/p]Буферизация, proxy_redirect и таймауты - управляем потоком

Nginx по умолчанию буферизует ответ бэкенда: proxy_buffering on. Что это значит на практике. Бэкенд (медленный PHP-FPM) выплёвывает ответ Nginx максимально быстро, Nginx складывает его в буферы (в память, при переполнении - во временный файл) и освобождает бэкенд. Дальше Nginx уже сам, не торопясь, отдаёт ответ медленному мобильному клиенту на 3G. Бэкенд не держит дорогой воркер всё время, пока клиент качает - это спасает пул процессов приложения. Настройки буферов из эталона:

Код: Выделить всё

proxy_buffering         on;
proxy_buffer_size       16k;
proxy_buffers           16 16k;
proxy_busy_buffers_size 32k;
proxy_buffer_size - буфер под заголовки ответа (16k хватает на жирные куки и заголовки). proxy_buffers 16 16k - 16 буферов по 16k под тело. Если ответ больше - уходит в proxy_temp_path на диск.

Когда буферизацию выключать. Есть сценарии, где буфер - враг: стриминг, Server-Sent Events (SSE), long-polling, прогресс-бары, чат на потоке токенов от LLM. Там клиент должен получать данные сразу, байт за байтом, а не ждать, пока Nginx накопит буфер. Для таких location:

Код: Выделить всё

location /stream/ {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 1h;
}
С proxy_buffering off Nginx отдаёт клиенту куски ответа по мере поступления от бэкенда. Цена - бэкенд-воркер занят всё время стрима. Это осознанный размен: для SSE/стрима выключаем, для обычного API - оставляем on.

proxy_request_buffering - зеркальная штука, но про тело запроса, не ответа. По умолчанию on: Nginx сначала целиком вычитывает тело запроса от клиента (например, загрузку файла) и только потом начинает слать бэкенду. Это защищает бэкенд от медленных клиентов (slowloris на upload). Но при загрузке гигабайтных файлов это значит, что Nginx буферизует весь файл (в память/на диск) перед отправкой. Для больших стриминговых загрузок выключают: proxy_request_buffering off - тогда Nginx стримит тело бэкенду по мере получения. Решай по задаче.

proxy_redirect - чинит заголовок Location в ответе. Бэкенд за прокси не знает внешнего адреса и может прислать редирект на свой внутренний http://127.0.0.1:9000/login. Клиент по такому уйдёт в никуда. proxy_redirect переписывает Location и Refresh в ответе:

Код: Выделить всё

proxy_redirect http://127.0.0.1:9000/ https://api.example.com/;
По умолчанию стоит proxy_redirect default - Nginx сам пытается переписать на основе proxy_pass и location, и часто этого хватает. Но если бэкенд отдаёт абсолютные URL не по шаблону - правишь руками.

Таймауты - последний рубеж. Из эталона:

Код: Выделить всё

proxy_connect_timeout 5s;
proxy_send_timeout    10s;
proxy_read_timeout    10s;
proxy_next_upstream         error timeout;
proxy_next_upstream_tries   3;
proxy_connect_timeout 5s - сколько ждём установки TCP-коннекта к бэкенду (не должен быть большим: если за 5 сек не подключились - бэкенд мёртв). proxy_read_timeout 10s - максимум между двумя чтениями из бэкенда (не суммарное время ответа, а пауза между байтами). proxy_send_timeout - то же для отправки. proxy_next_upstream error timeout + tries 3 - если бэкенд отвалился по ошибке или таймауту, попробовать следующий сервер в upstream, до 3 попыток. Внимание: для неидемпотентных POST включать переброс на следующий апстрим надо осознанно (можно словить двойное выполнение) - по умолчанию Nginx не ретраит POST, и это правильно.

Типичные грабли nginx прокси
  • Слеш в proxy_pass. Самая частая боль. /api/ -> proxy_pass http://up; отдаёт /api/users, а proxy_pass http://up/; отдаёт /users. Перепутал - получил 404 или дубль префикса. Всегда сверяй слеши location и proxy_pass.
  • Забыл Host $host. Бэкенд с виртуалхостами отдаёт дефолтный сайт или 404, потому что в Host прилетел 127.0.0.1.
  • Забыл X-Forwarded-Proto. Бесконечные редиректы https -> http -> https, mixed content, сломанная генерация ссылок во фреймворке.
  • Доверие к X-Forwarded-For без realip. Клиент подделывает IP, твой rate limit и бан по IP обходятся в один заголовок.
  • Capture-группы и переменные в proxy_pass. Как только в proxy_pass появляется переменная (proxy_pass http://$backend;), Nginx начинает резолвить имя через resolver на каждый запрос и НЕ использует кэш upstream - нужен resolver в конфиге, иначе ошибка. Это другой режим работы, осторожно.
  • proxy_buffering off везде "для скорости". Миф. Для обычного API буферизация ускоряет (освобождает бэкенд), выключать её надо только для стримов.
  • rewrite ломает отрезание префикса. Если в location есть rewrite, меняющий $uri, режим "отрезать префикс" в proxy_pass перестаёт работать - URI пойдёт уже переписанный.
Мини-лаба: подними обратный прокси руками

Повторяем по шагам, нужен любой Linux с nginx и питоном.
  • Шаг 1. Подними фейковый бэкенд, который покажет, что до него дошло. В отдельном терминале запусти простой эхо-сервер - подойдёт даже

    Код: Выделить всё

    python3 -m http.server 9000
    (он логирует пришедший путь в консоль) либо мини-приложение на flask, печатающее заголовки.
  • Шаг 2. Создай конфиг proxy.conf:

    Код: Выделить всё

    server {
        listen 8081;
        location /api/ {
            proxy_pass http://127.0.0.1:9000/;
            proxy_set_header Host              $host;
            proxy_set_header X-Real-IP         $remote_addr;
            proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
    }
    
  • Шаг 3. Проверь синтаксис и перечитай конфиг:

    Код: Выделить всё

    nginx -t && nginx -s reload
  • Шаг 4. Дёрни прокси:

    Код: Выделить всё

    curl -i http://127.0.0.1:8081/api/users/42
    . Смотри в лог бэкенда - он должен показать запрос /users/42 (префикс /api/ отрезан слешем).
  • Шаг 5. Убери слеш в proxy_pass (стало http://127.0.0.1:9000;), reload, повтори curl. Теперь бэкенд увидит /api/users/42. Ты только что руками воспроизвёл правило про слеш.
  • Шаг 6. На бэкенде распечатай пришедшие заголовки и убедись, что в них есть Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto с твоим IP.
  • Шаг 7. Включи

    Код: Выделить всё

    proxy_buffering off;
    в location и сравни поведение на стрим-эндпоинте (если есть). На обычном ответе разницы не заметишь - и это нормально.
Контрольные вопросы
  • location /app/, запрос /app/css/main.css. Что увидит бэкенд при proxy_pass http://up; и что при proxy_pass http://up/static/; ?
  • Зачем нужен X-Forwarded-Proto $scheme и что сломается во фреймворке, если его не передать?
  • Что изменилось в nginx 1.29.7 / 1.30 в дефолтном соединении к бэкенду и зачем тогда всё равно писать proxy_http_version 1.1 и Connection ""?
  • В каком сценарии proxy_buffering надо выключить и какую цену за это платит бэкенд?
Итог

Обратный прокси - это сердце Nginx 2026. Директива proxy_pass решает, куда уйдёт запрос, а один слеш в её конце решает, с каким путём он туда придёт - запомни правило про префикс намертво. Бэкенд за прокси слеп: расскажи ему правду через proxy_set_header Host $host, X-Real-IP, X-Forwarded-For и X-Forwarded-Proto, и не доверяй этим заголовкам без realip. С версии 1.30 keepalive к бэкенду включён по умолчанию, но явные proxy_http_version 1.1 и пустой Connection оставляем для совместимости и контроля пула. Буферизацию держим включённой для обычного API и выключаем точечно для стримов и SSE. Таймауты - твой предохранитель от зависших бэкендов. Это фундамент, на котором дальше вырастут балансировка, кэш и полноценный API-gateway.
👍4 ❤️5 🔥1 😄 🤔
Аватара пользователя
rawphoenix
Сообщения: 1
Зарегистрирован: 05 июн 2026, 05:04

Re: Reverse proxy: proxy_pass и проброс запроса

Сообщение rawphoenix »

Блин, я годами копировал proxy_pass со слешем и не понимал почему иногда 404. Оказывается location /api/ + proxy_pass без слеша = он не режет префикс. Спасибо, разложил по полкам.
👍 ❤️2 🔥1 😄 🤔1
Аватара пользователя
nuisance1077
Сообщения: 1
Зарегистрирован: 19 май 2026, 22:40

Re: Reverse proxy: proxy_pass и проброс запроса

Сообщение nuisance1077 »

Подскажите по дефолту с 1.30: если keepalive теперь из коробки, зачем держать keepalive 64 в upstream? Я правильно понял что дефолтные 32 на воркер просто мало для нагруженного API и поэтому ставим явно?
👍2 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Сжатие: gzip, brotli и zstd
Следующая глава →
upstream и балансировка нагрузки

Все главы курса «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Поделиться темой: ✈ Telegram VK
Похожие запросы: nginx в Dockernginx как API-gateway и Kubernetes IngressОшибка nginx 502 Bad Gateway: причины и решениеrealip в nginx: реальный IP за прокси и CDNnginx и PHP-FPM: настройка FastCGIupstream и балансировка нагрузки в nginx

Вернуться в «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя