Nginx как reverse proxy: проксирование запросов к backend-сервисам с правильной передачей заголовков

Reverse proxy на Nginx принимает запросы клиентов и перенаправляет их внутренним сервисам, скрывая топологию инфраструктуры от внешнего мира. Ключевая задача при настройке — не просто переслать запрос, а корректно передать заголовки, чтобы backend знал реальный IP клиента, оригинальный хост и протокол соединения.

Минимальная рабочая конфигурация​


Базовый блок 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-IPIP-адрес непосредственного клиентаЛогирование, 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'ов.

Источники​


 
Назад
Верх Низ