Reverse proxy на Nginx принимает запросы клиентов и перенаправляет их внутренним сервисам, скрывая топологию инфраструктуры от внешнего мира. Ключевая задача при настройке — не просто переслать запрос, а корректно передать заголовки, чтобы backend знал реальный IP клиента, оригинальный хост и протокол соединения.
Базовый блок
Эта конфигурация перенаправляет все запросы к
По умолчанию Nginx заменяет заголовок
Разница между
Передача заголовка
Если backend использует WebSocket, стандартной передачи заголовков недостаточно:
Когда backend представлен несколькими экземплярами, используется блок
По умолчанию Nginx использует round-robin. Доступны также
Значения по умолчанию подходят не всегда. Если backend обрабатывает запросы долго (отчёты, экспорт данных), стоит явно задать таймауты:
Буферизация ответа управляется директивой
Типичная схема: клиент → HTTPS → Nginx → HTTP → backend. В этом случае
Backend видит
Причина: отсутствует
Решение: добавить оба заголовка и убедиться, что приложение читает именно
Redirect на
Причина: backend генерирует абсолютные URL, опираясь на протокол входящего соединения. Между Nginx и backend соединение идёт по HTTP, поэтому backend считает, что протокол —
Решение: передать
Возможные причины:
Диагностика:
Backend не ответил за отведённое время. Увеличить
Nginx закрывает неактивные соединения по
Перед применением изменений:
Команда проверяет синтаксис и пытается открыть указанные файлы (сертификаты, upstream-адреса не резолвятся на этапе
Для проверки того, какие заголовки реально доходят до backend, удобно поднять временный echo-сервис или использовать
Если backend написан на Python (Flask/FastAPI) или Node.js, достаточно вывести
Когда несколько
Это снижает риск расхождения конфигураций при добавлении новых location'ов.
Минимальная рабочая конфигурация
Базовый блок
location с директивой proxy_pass:
NGINX:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080;
}
}
Эта конфигурация перенаправляет все запросы к
example.com на локальный сервис, слушающий порт 8080. Однако без дополнительных директив backend получит искажённую информацию о клиенте.Передача заголовков: что и зачем
По умолчанию Nginx заменяет заголовок
Host на значение из proxy_pass (в примере выше — 127.0.0.1:8080). Backend, который использует Host для маршрутизации или генерации ссылок, получит неверные данные.Обязательный набор заголовков
NGINX:
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
| Заголовок | Что передаёт | Зачем нужен |
|---|---|---|
Host | Оригинальный домен из запроса клиента | Виртуальный хостинг, генерация абсолютных URL |
X-Real-IP | IP-адрес непосредственного клиента | Логирование, rate limiting, геолокация |
X-Forwarded-For | Цепочка IP через все прокси | Аудит, определение источника при нескольких hop'ах |
X-Forwarded-Proto | Протокол оригинального соединения (http или https) | Корректная генерация redirect'ов, проверка secure cookie |
Разница между $remote_addr и $proxy_add_x_forwarded_for
$remote_addr — это IP непосредственного собеседника Nginx. Если перед Nginx стоит ещё один балансировщик (например, cloud LB), $remote_addr покажет его IP, а не клиента.$proxy_add_x_forwarded_for берёт существующий заголовок X-Forwarded-For из входящего запроса и дописывает $remote_addr через запятую. Это позволяет сохранить полную цепочку.Передача заголовка Connection и Upgrade для WebSocket
Если backend использует WebSocket, стандартной передачи заголовков недостаточно:
NGINX:
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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 обязателен: HTTP/1.0 не поддерживает keep-alive и механизм Upgrade.Upstream-блоки и балансировка
Когда backend представлен несколькими экземплярами, используется блок
upstream:
NGINX:
upstream app_backend {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080;
}
server {
listen 443 ssl;
server_name example.com;
location / {
proxy_pass http://app_backend;
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 использует round-robin. Доступны также
least_conn (запрос уходит на сервер с наименьшим числом активных соединений) и ip_hash (привязка клиента к серверу по IP для sticky sessions).
NGINX:
upstream app_backend {
least_conn;
server 10.0.1.10:8080;
server 10.0.1.11:8080;
}
Таймауты и буферизация
Значения по умолчанию подходят не всегда. Если backend обрабатывает запросы долго (отчёты, экспорт данных), стоит явно задать таймауты:
NGINX:
location /api/export/ {
proxy_pass http://app_backend;
proxy_connect_timeout 10s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
}
proxy_connect_timeout— время на установку TCP-соединения с backend.
proxy_send_timeout— таймаут между двумя последовательными операциями записи в сторону backend.
proxy_read_timeout— таймаут между двумя последовательными операциями чтения ответа от backend.
Буферизация ответа управляется директивой
proxy_buffering. По умолчанию она включена: Nginx принимает ответ от backend целиком (или до заполнения буфера), а затем отдаёт клиенту. Для streaming-ответов (SSE, chunked transfer) буферизацию отключают:
NGINX:
location /api/stream/ {
proxy_pass http://app_backend;
proxy_buffering off;
proxy_cache off;
}
Терминация TLS на Nginx
Типичная схема: клиент → HTTPS → Nginx → HTTP → backend. В этом случае
X-Forwarded-Proto критически важен — без него backend не узнает, что оригинальное соединение было зашифровано.
NGINX:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
Типичные ошибки и их симптомы
Backend видит 127.0.0.1 вместо IP клиента
Причина: отсутствует
proxy_set_header X-Real-IP или X-Forwarded-For. Приложение на backend читает REMOTE_ADDR из CGI-переменных, а там — адрес Nginx.Решение: добавить оба заголовка и убедиться, что приложение читает именно
X-Real-IP или X-Forwarded-For, а не REMOTE_ADDR.Redirect на http:// вместо https://
Причина: backend генерирует абсолютные URL, опираясь на протокол входящего соединения. Между Nginx и backend соединение идёт по HTTP, поэтому backend считает, что протокол —
http.Решение: передать
X-Forwarded-Proto $scheme и настроить приложение на использование этого заголовка.502 Bad Gateway
Возможные причины:
- Backend не запущен или слушает другой порт.
proxy_passуказывает на неверный адрес.
- Backend отклоняет соединение из-за перегрузки.
Диагностика:
curl -v http://127.0.0.1:8080/ напрямую к backend, проверка error.log Nginx.504 Gateway Timeout
Backend не ответил за отведённое время. Увеличить
proxy_read_timeout или оптимизировать запрос на стороне приложения.WebSocket обрывается через 60 секунд
Nginx закрывает неактивные соединения по
proxy_read_timeout. Для WebSocket-соединений, где нет постоянного обмена данными, нужно либо увеличить таймаут, либо реализовать ping/pong на уровне приложения.Проверка конфигурации
Перед применением изменений:
Bash:
nginx -t
Команда проверяет синтаксис и пытается открыть указанные файлы (сертификаты, upstream-адреса не резолвятся на этапе
-t). Если вывод syntax is ok и test is successful, можно перезагружать:
Bash:
systemctl reload nginx
reload применяет конфигурацию без разрыва активных соединений — Nginx запускает новые worker-процессы и плавно завершает старые.Для проверки того, какие заголовки реально доходят до backend, удобно поднять временный echo-сервис или использовать
curl с заголовком Host:
Bash:
curl -H "Host: example.com" http://127.0.0.1:8080/debug/headers
Если backend написан на Python (Flask/FastAPI) или Node.js, достаточно вывести
request.headers в лог — сразу видно, что пришло.Вынос повторяющихся директив
Когда несколько
location проксируют на один backend с одинаковыми заголовками, дублирование убирают через include:
NGINX:
## /etc/nginx/snippets/proxy_headers.conf
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:
location / {
proxy_pass http://app_backend;
include /etc/nginx/snippets/proxy_headers.conf;
}
Это снижает риск расхождения конфигураций при добавлении новых location'ов.
