Обратный прокси: proxy_pass и поток запроса
proxy_pass — это волшебное слово, превращающее Nginx из раздатчика файлов в посредника между интернетом и твоим приложением.
«Клиент думает, что говорит с Nginx. Бэкенд думает, что говорит с Nginx. А Nginx — посередине, и оба счастливы.»
Обратный прокси — главная роль Nginx в современном вебе. Идея проста: Nginx принимает запрос от клиента и пересылает его внутреннему приложению (бэкенду), а ответ бэкенда возвращает клиенту. Директива, которая это включает, — proxy_pass.
Базовый проксирующий конфиг
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8000; # приложение на localhost:8000
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;
}
}
Здесь Nginx слушает порт 80, а всё, что приходит на /, отправляет приложению на 127.0.0.1:8000. Приложение слушает только localhost — снаружи оно недоступно.
Поток запроса
Клиент Nginx (:80) Бэкенд (:8000)
| | |
| GET / -------> | |
| | GET / + заголовки -->|
| | | (генерирует ответ)
| | <-- 200 OK ----------|
| <-- 200 OK -----| |
| | |
Клиент общается только с Nginx и не подозревает о существовании бэкенда. Бэкенд видит запрос от Nginx. Заголовки X-Real-IP и X-Forwarded-For нужны, чтобы приложение всё-таки узнало реальный IP клиента — иначе оно увидит лишь IP самого Nginx.
Коварный слеш в proxy_pass
# БЕЗ слеша: префикс location СОХРАНЯЕТСЯ
location /api/ {
proxy_pass http://127.0.0.1:8000;
}
# /api/users -> бэкенд получит /api/users
# СО слешем: префикс location ЗАМЕНЯЕТСЯ
location /api/ {
proxy_pass http://127.0.0.1:8000/;
}
# /api/users -> бэкенд получит /users (без /api)
Как работает под капотом
Получив запрос, Nginx устанавливает новое соединение с бэкендом и пересылает запрос с теми заголовками, что ты задал через proxy_set_header. Ответ бэкенда буферизуется (по умолчанию proxy_buffering on): Nginx может принять ответ целиком в буфер и отдавать клиенту в его темпе, освобождая бэкенд. Так медленный клиент не держит ценный воркер приложения занятым — Nginx «впитывает» ответ и кормит клиента сам.
Частые ошибки
- Забыть
proxy_set_header Host $host;. Многие приложения по Host строят ссылки и выбирают сайт; без него получишь странности. - Перепутать слеш в proxy_pass. Лишний или недостающий
/ломает путь, и бэкенд отдаёт 404. - Не передать реальный IP. Без
X-Forwarded-Forприложение залогирует IP Nginx, а не пользователя. - Бэкенд слушает 0.0.0.0. Тогда его можно дёрнуть напрямую в обход Nginx — пусть слушает только 127.0.0.1.
Best practices
- Всегда задавай связку
Host,X-Real-IP,X-Forwarded-For,X-Forwarded-Proto. - Вынеси эти proxy_set_header в snippet и подключай через include.
- Бэкенд — только на 127.0.0.1; наружу смотрит лишь Nginx.
Почему буферизация — это не мелочь
Может показаться, что буферизация ответа — деталь, но именно она часто определяет, сколько пользователей выдержит твой бэкенд. Представь приложение с дорогим воркером (например, Python с GIL или PHP-FPM с ограниченным пулом). Без Nginx такой воркер, отдав ответ, вынужден ждать, пока медленный мобильный клиент дочитает его байт за байтом — и всё это время воркер занят и недоступен другим. С буферизацией Nginx мгновенно «впитывает» весь ответ в свою память, освобождает воркер бэкенда, и уже сам, своими дешёвыми событийными воркерами, докармливает ответ медленному клиенту. Один и тот же бэкенд за Nginx обслуживает в разы больше запросов в секунду.
Та же логика работает и на входе: Nginx буферизует медленно приходящие запросы (большая загрузка файла по тонкому каналу) и отдаёт их бэкенду одним быстрым куском. Это защита от целого класса проблем, включая медленные атаки вроде Slowloris, когда злоумышленник нарочно тянет соединения, чтобы исчерпать ресурсы сервера. Nginx с его событийной моделью держит тысячи таких медленных соединений почти бесплатно, а бэкенд их вообще не видит. Поэтому даже для одного-единственного бэкенда обратный прокси перед ним — это не лишняя прослойка, а серьёзный буфер прочности и безопасности.
Итоги
Обратный прокси через proxy_pass делает Nginx посредником: клиент общается с Nginx, Nginx — с бэкендом. Заголовки X-Forwarded-* доносят до приложения реальные данные клиента, а буферизация защищает бэкенд от медленных клиентов. Слеш в proxy_pass решает, сохранять ли префикс пути. Дальше — тонкая настройка проксирования: таймауты, буферы, WebSocket.